💡 style: switched to Ariakit's tooltip (#3748)

* inital Tooltip implementation and test

* style(tooltip): L/R sidePanel and Nav

* style(tooltip): unarchive button; refactor: `useArchiveHandler` and `ArchiveButton`

* style(tooltip): Delete button

* refactor: remove unused className prop in DeleteButton component

* style(tooltip): finish final tooltip and fix bookmark edit and delete button

* refactor(ui): remove TooltipTest and DropDownMenu component and unused imports

* style: update mobile UI

* fix: sidePanel icon not showing

* feat(AttachFile): add tooltip

* fix(NavToggle): remove button
without this button, kb users don't have to manually press 2 times to change the focus
Also, tooltips with buttons focus don't trigger

* fix: right side panel issue with double button

* fix: merge issues

* fix: sharedLink table issue

* chore: update ariakit and framer-motion version

* a11y: kb toggle for sidebar

* feat: tooltip for some buttons
This commit is contained in:
Marco Beretta 2024-09-13 08:59:09 -04:00 committed by GitHub
parent e293ff63f9
commit 4ef5ae6f71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 747 additions and 967 deletions

View file

@ -14,9 +14,10 @@ import store from '~/store';
type BookmarkNavProps = {
tags: string[];
setTags: (tags: string[]) => void;
isSmallScreen: boolean;
};
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps) => {
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags, isSmallScreen }: BookmarkNavProps) => {
const localize = useLocalize();
const location = useLocation();
@ -40,6 +41,7 @@ const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps)
className={cn(
'mt-text-sm flex h-10 w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors duration-200 hover:bg-surface-hover',
open ? 'bg-surface-hover' : '',
isSmallScreen ? 'h-14 rounded-2xl' : '',
)}
data-testid="bookmark-menu"
>

View file

@ -15,11 +15,11 @@ export default function MobileNav({
const { title = 'New Chat' } = conversation || {};
return (
<div className="border-token-border-medium bg-token-main-surface-primary sticky top-0 z-10 flex min-h-[40px] items-center justify-center border-b bg-white pl-1 dark:bg-gray-800 dark:text-white md:hidden md:hidden">
<div className="bg-token-main-surface-primary sticky top-0 z-10 flex min-h-[40px] items-center justify-center bg-white pl-1 dark:bg-gray-800 dark:text-white md:hidden">
<button
type="button"
data-testid="mobile-header-new-chat-button"
className="inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-800 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white active:opacity-50 dark:hover:text-white"
className="inline-flex size-10 items-center justify-center rounded-full hover:bg-surface-hover"
onClick={() =>
setNavVisible((prev) => {
localStorage.setItem('navVisible', JSON.stringify(!prev));
@ -49,7 +49,7 @@ export default function MobileNav({
</h1>
<button
type="button"
className="inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-800 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white active:opacity-50 dark:hover:text-white"
className="inline-flex size-10 items-center justify-center rounded-full hover:bg-surface-hover"
onClick={() => newConversation()}
>
<svg

View file

@ -14,7 +14,6 @@ import {
useConversations,
} from '~/hooks';
import { useConversationsInfiniteQuery } from '~/data-provider';
import { TooltipProvider, Tooltip } from '~/components/ui';
import { Conversations } from '~/components/Conversations';
import BookmarkNav from './Bookmarks/BookmarkNav';
import AccountSettings from './AccountSettings';
@ -130,111 +129,112 @@ const Nav = ({
};
return (
<TooltipProvider delayDuration={250}>
<Tooltip>
<div
data-testid="nav"
className={
'nav active max-w-[320px] flex-shrink-0 overflow-x-hidden bg-surface-primary-alt md:max-w-[260px]'
}
style={{
width: navVisible ? navWidth : '0px',
visibility: navVisible ? 'visible' : 'hidden',
transition: 'width 0.2s, visibility 0.2s',
}}
>
<div className="h-full w-[320px] md:w-[260px]">
<div className="flex h-full min-h-0 flex-col">
<>
<div
data-testid="nav"
className={
'nav active max-w-[320px] flex-shrink-0 overflow-x-hidden bg-surface-primary-alt md:max-w-[260px]'
}
style={{
width: navVisible ? navWidth : '0px',
visibility: navVisible ? 'visible' : 'hidden',
transition: 'width 0.2s, visibility 0.2s',
}}
>
<div className="h-full w-[320px] md:w-[260px]">
<div className="flex h-full min-h-0 flex-col">
<div
className={cn(
'flex h-full min-h-0 flex-col transition-opacity',
isToggleHovering && !isSmallScreen ? 'opacity-50' : 'opacity-100',
)}
>
<div
className={cn(
'flex h-full min-h-0 flex-col transition-opacity',
isToggleHovering && !isSmallScreen ? 'opacity-50' : 'opacity-100',
'scrollbar-trigger relative h-full w-full flex-1 items-start border-white/20',
)}
>
<div
className={cn(
'scrollbar-trigger relative h-full w-full flex-1 items-start border-white/20',
)}
<nav
id="chat-history-nav"
aria-label={localize('com_ui_chat_history')}
className="flex h-full w-full flex-col px-3 pb-3.5"
>
<nav
id="chat-history-nav"
aria-label={localize('com_ui_chat_history')}
className="flex h-full w-full flex-col px-3 pb-3.5"
<div
className={cn(
'-mr-2 flex-1 flex-col overflow-y-auto pr-2 transition-opacity duration-500',
isHovering ? '' : 'scrollbar-transparent',
)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
ref={containerRef}
>
<div
className={cn(
'-mr-2 flex-1 flex-col overflow-y-auto pr-2 transition-opacity duration-500',
isHovering ? '' : 'scrollbar-transparent',
)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
ref={containerRef}
>
{isSmallScreen == true ? (
<div className="pt-3.5">
{isSearchEnabled === true && (
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
)}
{hasAccessToBookmarks === true && (
<BookmarkNav tags={tags} setTags={setTags} />
)}
</div>
) : (
<NewChat
toggleNav={itemToggleNav}
subHeaders={
<>
{isSearchEnabled === true && (
<SearchBar
clearSearch={clearSearch}
isSmallScreen={isSmallScreen}
/>
)}
{hasAccessToBookmarks === true && (
<BookmarkNav tags={tags} setTags={setTags} />
)}
</>
}
/>
)}
<Conversations
conversations={conversations}
moveToTop={moveToTop}
{isSmallScreen == true ? (
<div className="pt-3.5">
{isSearchEnabled === true && (
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
)}
{hasAccessToBookmarks === true && (
<BookmarkNav
tags={tags}
setTags={setTags}
isSmallScreen={isSmallScreen}
/>
)}
</div>
) : (
<NewChat
toggleNav={itemToggleNav}
subHeaders={
<>
{isSearchEnabled === true && (
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
)}
<BookmarkNav
tags={tags}
setTags={setTags}
isSmallScreen={isSmallScreen}
/>
</>
}
/>
{(isFetchingNextPage || showLoading) && (
<Spinner className={cn('m-1 mx-auto mb-4 h-4 w-4 text-text-primary')} />
)}
</div>
<AccountSettings />
</nav>
</div>
)}
<Conversations
conversations={conversations}
moveToTop={moveToTop}
toggleNav={itemToggleNav}
/>
{(isFetchingNextPage || showLoading) && (
<Spinner className={cn('m-1 mx-auto mb-4 h-4 w-4 text-text-primary')} />
)}
</div>
<AccountSettings />
</nav>
</div>
</div>
</div>
</div>
<NavToggle
isHovering={isToggleHovering}
setIsHovering={setIsToggleHovering}
onToggle={toggleNavVisible}
navVisible={navVisible}
className="fixed left-0 top-1/2 z-40 hidden md:flex"
/>
<div
role="button"
tabIndex={0}
className={`nav-mask ${navVisible ? 'active' : ''}`}
onClick={toggleNavVisible}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
toggleNavVisible();
}
}}
aria-label="Toggle navigation"
/>
</Tooltip>
</TooltipProvider>
</div>
<NavToggle
isHovering={isToggleHovering}
setIsHovering={setIsToggleHovering}
onToggle={toggleNavVisible}
navVisible={navVisible}
className="fixed left-0 top-1/2 z-40 hidden md:flex"
/>
<div
role="button"
tabIndex={0}
className={`nav-mask ${navVisible ? 'active' : ''}`}
onClick={toggleNavVisible}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
toggleNavVisible();
}
}}
aria-label="Toggle navigation"
/>
</>
);
};

View file

@ -1,5 +1,5 @@
import { TooltipTrigger, TooltipContent } from '~/components/ui';
import { useLocalize, useLocalStorage } from '~/hooks';
import { TooltipAnchor } from '~/components/ui';
import { cn } from '~/utils';
export default function NavToggle({
@ -15,7 +15,6 @@ export default function NavToggle({
const transition = {
transition: 'transform 0.3s ease, opacity 0.2s ease',
};
const [newUser] = useLocalStorage('newUser', true);
const rotationDegree = 15;
const rotation = isHovering || !navVisible ? `${rotationDegree}deg` : '0deg';
@ -33,46 +32,44 @@ export default function NavToggle({
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
>
<TooltipTrigger asChild>
<button
onClick={onToggle}
id={`toggle-${side}-nav`}
aria-label={`toggle-${side === 'left' ? 'chat-history' : 'controls'}-nav`}
>
<span className="" data-state="closed">
<div
className="flex h-[72px] w-8 items-center justify-center"
style={{ ...transition, opacity: isHovering ? 1 : 0.25 }}
>
<div className="flex h-6 w-6 flex-col items-center">
{/* Top bar */}
<div
className="h-3 w-1 rounded-full bg-black dark:bg-white"
style={{
...transition,
transform: `translateY(0.15rem) rotate(${topBarRotation}) translateZ(0px)`,
}}
/>
{/* Bottom bar */}
<div
className="h-3 w-1 rounded-full bg-black dark:bg-white"
style={{
...transition,
transform: `translateY(-0.15rem) rotate(${bottomBarRotation}) translateZ(0px)`,
}}
/>
</div>
<TooltipAnchor
side={side === 'right' ? 'left' : 'right'}
aria-label={`toggle-${side === 'left' ? 'chat-history' : 'controls'}-nav`}
id={`toggle-${side}-nav`}
onClick={onToggle}
role="button"
description={
navVisible ? localize('com_nav_close_sidebar') : localize('com_nav_open_sidebar')
}
className="flex cursor-pointer items-center justify-center"
tabIndex={0}
>
<span className="" data-state="closed">
<div
className="flex h-[72px] w-8 items-center justify-center"
style={{ ...transition, opacity: isHovering ? 1 : 0.25 }}
>
<div className="flex h-6 w-6 flex-col items-center">
{/* Top bar */}
<div
className="h-3 w-1 rounded-full bg-black dark:bg-white"
style={{
...transition,
transform: `translateY(0.15rem) rotate(${topBarRotation}) translateZ(0px)`,
}}
/>
{/* Bottom bar */}
<div
className="h-3 w-1 rounded-full bg-black dark:bg-white"
style={{
...transition,
transform: `translateY(-0.15rem) rotate(${bottomBarRotation}) translateZ(0px)`,
}}
/>
</div>
<TooltipContent
forceMount={newUser ? true : undefined}
side={side === 'right' ? 'left' : 'right'}
sideOffset={4}
>
{navVisible ? localize('com_nav_close_sidebar') : localize('com_nav_open_sidebar')}
</TooltipContent>
</span>
</button>
</TooltipTrigger>
</div>
</span>
</TooltipAnchor>
</div>
);
}

View file

@ -2,14 +2,14 @@ import { Search } from 'lucide-react';
import { useRecoilValue } from 'recoil';
import { useNavigate } from 'react-router-dom';
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
import type { TConversation } from 'librechat-data-provider';
import { getEndpointField, getIconEndpoint, getIconKey } from '~/utils';
import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
import { useLocalize, useNewConvo } from '~/hooks';
import { TooltipAnchor } from '~/components/ui';
import { NewChatIcon } from '~/components/svg';
import store from '~/store';
import type { TConversation } from 'librechat-data-provider';
const NewChatButtonIcon = ({ conversation }: { conversation: TConversation | null }) => {
const searchQuery = useRecoilValue(store.searchQuery);
@ -80,43 +80,36 @@ export default function NewChat({
};
return (
<TooltipProvider delayDuration={250}>
<Tooltip>
<div className="sticky left-0 right-0 top-0 z-50 bg-surface-primary-alt pt-3.5">
<div className="pb-0.5 last:pb-0" style={{ transform: 'none' }}>
<a
href="/"
tabIndex={0}
data-testid="nav-new-chat"
onClick={clickHandler}
className="group flex h-10 items-center gap-2 rounded-lg px-2 font-medium transition-colors duration-200 hover:bg-surface-hover"
aria-label={localize('com_ui_new_chat')}
>
<NewChatButtonIcon conversation={conversation} />
<div className="grow overflow-hidden text-ellipsis whitespace-nowrap text-sm text-text-primary">
{localize('com_ui_new_chat')}
</div>
<div className="flex gap-3">
<span className="flex items-center" data-state="closed">
<TooltipTrigger asChild>
<button
id="nav-new-chat-btn"
aria-label={localize('com_ui_new_chat')}
className="text-text-primary"
>
<NewChatIcon className="size-5" />
</button>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={20}>
{localize('com_ui_new_chat')}
</TooltipContent>
</span>
</div>
</a>
<div className="sticky left-0 right-0 top-0 z-50 bg-surface-primary-alt pt-3.5">
<div className="pb-0.5 last:pb-0" style={{ transform: 'none' }}>
<a
href="/"
tabIndex={0}
data-testid="nav-new-chat"
onClick={clickHandler}
className="group flex h-10 items-center gap-2 rounded-lg px-2 font-medium transition-colors duration-200 hover:bg-surface-hover"
aria-label={localize('com_ui_new_chat')}
>
<NewChatButtonIcon conversation={conversation} />
<div className="grow overflow-hidden text-ellipsis whitespace-nowrap text-sm text-text-primary">
{localize('com_ui_new_chat')}
</div>
{subHeaders != null ? subHeaders : null}
</div>
</Tooltip>
</TooltipProvider>
<div className="flex gap-3">
<span className="flex items-center" data-state="closed">
<TooltipAnchor
side="right"
id="nav-new-chat-btn"
aria-label="nav-new-chat-btn"
description={localize('com_ui_new_chat')}
className="text-text-primary"
>
<NewChatIcon className="size-5" />
</TooltipAnchor>
</span>
</div>
</a>
</div>
{subHeaders != null ? subHeaders : null}
</div>
);
}

View file

@ -61,7 +61,7 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) =
ref={ref}
className={cn(
'group relative mt-1 flex h-10 cursor-pointer items-center gap-3 rounded-lg border-border-medium px-3 py-2 text-text-primary transition-colors duration-200 focus-within:bg-surface-hover hover:bg-surface-hover',
isSmallScreen === true ? 'h-16 rounded-2xl' : '',
isSmallScreen === true ? 'mb-2 h-14 rounded-2xl' : '',
)}
>
{

View file

@ -1,20 +1,13 @@
import { useMemo, useState, MouseEvent } from 'react';
import { useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { MessageSquare, Link as LinkIcon } from 'lucide-react';
import { Link as LinkIcon } from 'lucide-react';
import type { SharedLinksResponse, TSharedLink } from 'librechat-data-provider';
import { useDeleteSharedLinkMutation, useSharedLinksInfiniteQuery } from '~/data-provider';
import { useAuthContext, useLocalize, useNavScrolling } from '~/hooks';
import { Spinner, TooltipAnchor, TrashIcon } from '~/components';
import { NotificationSeverity } from '~/common';
import { useToastContext } from '~/Providers';
import { cn } from '~/utils';
import {
Spinner,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
TrashIcon,
} from '~/components';
function SharedLinkDeleteButton({
shareId,
@ -36,7 +29,7 @@ function SharedLinkDeleteButton({
},
});
const handleDelete = async (e: MouseEvent<HTMLButtonElement>) => {
const handleDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
if (mutation.isLoading) {
return;
@ -46,36 +39,14 @@ function SharedLinkDeleteButton({
setIsDeleting(false);
};
return (
<TooltipProvider delayDuration={250}>
<Tooltip>
<TooltipTrigger asChild>
<button id="delete-shared-link" aria-label="Delete shared link" onClick={handleDelete}>
<TrashIcon />
</button>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={0}>
{localize('com_ui_delete')}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
function SourceChatButton({ conversationId }: { conversationId: string }) {
const localize = useLocalize();
return (
<TooltipProvider delayDuration={250}>
<Tooltip>
<TooltipTrigger asChild>
<Link to={`/c/${conversationId}`} target="_blank" rel="noreferrer">
<MessageSquare className="h-4 w-4 hover:text-gray-300" />
</Link>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={0}>
{localize('com_nav_source_chat')}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipAnchor
description={localize('com_ui_delete')}
id="delete-shared-link"
aria-label="Delete shared link"
onClick={handleDelete}
>
<TrashIcon className="size-4" />
</TooltipAnchor>
);
}
@ -114,15 +85,12 @@ function ShareLinkRow({ sharedLink }: { sharedLink: TSharedLink }) {
)}
>
{sharedLink.conversationId && (
<>
<SourceChatButton conversationId={sharedLink.conversationId} />
<div className={cn('h-4 w-4 cursor-pointer', !isDeleting && 'hover:text-gray-300')}>
<SharedLinkDeleteButton
shareId={sharedLink.shareId}
setIsDeleting={setIsDeleting}
/>
</div>
</>
<div className={cn('cursor-pointer', !isDeleting && 'hover:text-gray-300')}>
<SharedLinkDeleteButton
shareId={sharedLink.shareId}
setIsDeleting={setIsDeleting}
/>
</div>
)}
</div>
</div>

View file

@ -1,6 +1,6 @@
import { useLocalize } from '~/hooks';
import { Dialog, DialogTrigger } from '~/components/ui';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { OGDialog, OGDialogTrigger } from '~/components/ui';
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
import ShareLinkTable from './SharedLinkTable';
@ -11,19 +11,19 @@ export default function SharedLinks() {
<div className="flex items-center justify-between">
<div>{localize('com_nav_shared_links')}</div>
<Dialog>
<DialogTrigger asChild>
<OGDialog>
<OGDialogTrigger asChild>
<button className="btn btn-neutral relative ">
{localize('com_nav_shared_links_manage')}
</button>
</DialogTrigger>
<DialogTemplate
</OGDialogTrigger>
<OGDialogTemplate
title={localize('com_nav_shared_links')}
className="max-w-[1000px]"
showCancelButton={false}
main={<ShareLinkTable />}
/>
</Dialog>
</OGDialog>
</div>
);
}

View file

@ -1,17 +1,18 @@
import { useMemo, useState } from 'react';
import { useMemo, useState, useCallback } from 'react';
import { MessageCircle, ArchiveRestore } from 'lucide-react';
import { useConversationsInfiniteQuery } from '~/data-provider';
import { useAuthContext, useLocalize, useNavScrolling } from '~/hooks';
import ArchiveButton from '~/components/Conversations/ConvoOptions/ArchiveButton';
import DeleteButton from '~/components/Conversations/ConvoOptions/DeleteButton';
import { ConversationListResponse } from 'librechat-data-provider';
import { useAuthContext, useLocalize, useNavScrolling, useArchiveHandler } from '~/hooks';
import { DeleteButton } from '~/components/Conversations/ConvoOptions';
import { TooltipAnchor } from '~/components/ui';
import { Spinner } from '~/components/svg';
import { cn } from '~/utils';
import { ConversationListResponse } from 'librechat-data-provider';
export default function ArchivedChatsTable({ className }: { className?: string }) {
export default function ArchivedChatsTable() {
const localize = useLocalize();
const { isAuthenticated } = useAuthContext();
const [showLoading, setShowLoading] = useState(false);
const [conversationId, setConversationId] = useState<string | null>(null);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useConversationsInfiniteQuery(
{ pageNumber: '1', isArchived: true },
@ -30,14 +31,9 @@ export default function ArchivedChatsTable({ className }: { className?: string }
[data],
);
const classProp: { className?: string } = {
className: 'p-1 hover:text-black dark:hover:text-white',
};
if (className) {
classProp.className = className;
}
const archiveHandler = useArchiveHandler(conversationId ?? '', false, moveToTop);
if (!conversations || conversations.length === 0) {
if (!data || conversations.length === 0) {
return <div className="text-gray-300">{localize('com_nav_archived_chats_empty')}</div>;
}
@ -58,48 +54,52 @@ export default function ArchivedChatsTable({ className }: { className?: string }
</tr>
</thead>
<tbody>
{conversations.map((conversation) => (
<tr
key={conversation.conversationId}
className="border-b border-gray-200 text-sm font-normal dark:border-white/10"
>
<td className="flex items-center py-3 text-blue-800/70 dark:text-blue-500">
<MessageCircle className="mr-1 h-5 w-5" />
{conversation.title}
</td>
<td className="p-1">
<div className="flex justify-between">
<div className="flex justify-start dark:text-gray-200">
{new Date(conversation.createdAt).toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
})}
</div>
<div className="ml-auto mr-4 flex items-center justify-end gap-1 text-gray-400">
{conversation.conversationId && (
<>
<ArchiveButton
className="hover:text-black dark:hover:text-white"
{conversations.map((conversation) => {
if (!conversation.conversationId) {
return null;
}
return (
<tr
key={conversation.conversationId}
className="border-b border-gray-200 text-sm font-normal dark:border-white/10"
>
<td className="flex items-center py-3 text-blue-800/70 dark:text-blue-500">
<MessageCircle className="mr-1 h-5 w-5" />
{conversation.title}
</td>
<td className="p-1">
<div className="flex justify-between">
<div className="flex justify-start dark:text-gray-200">
{new Date(conversation.createdAt).toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
})}
</div>
<div className="ml-auto mr-4 flex items-center justify-end gap-1 text-gray-400">
<TooltipAnchor
description={localize('com_ui_unarchive')}
onClick={() => {
setConversationId(conversation.conversationId);
archiveHandler();
}}
className="cursor-pointer hover:text-black dark:hover:text-white"
>
<ArchiveRestore className="size-4 hover:text-gray-300" />
</TooltipAnchor>
<div className="size-5 hover:text-gray-300">
<DeleteButton
conversationId={conversation.conversationId}
retainView={moveToTop}
shouldArchive={false}
icon={<ArchiveRestore className="h-4 w-4 hover:text-gray-300" />}
title={conversation.title ?? ''}
/>
<div className="h-5 w-5 hover:text-gray-300">
<DeleteButton
conversationId={conversation.conversationId}
retainView={moveToTop}
title={conversation.title ?? ''}
/>
</div>
</>
)}
</div>
</div>
</div>
</div>
</td>
</tr>
))}
</td>
</tr>
);
})}
</tbody>
</table>
{(isFetchingNextPage || showLoading) && (