💡 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

@ -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) && (