mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 10:20:15 +01:00
👐 style: Improve a11y/theming for Settings Dialog, Dropdown Menus; fix: SearchBar focus issues (#4091)
* fix: cursor pointer not applying correct in the root component * fix: add cursor-not-allowed to disabled state in SendButton component * feat: update Dropdown to ariakit and changed LLM error's style * feat: switched to ariakit's Dropdown and style improvements * feat: archive updates * refactor: delete conversations in archive * refactor: settings * add cool settings animation * a11y: settings update * style: update settings * style: settings account settings menu; a11y(AccountSettings): switched to AriaKit * a11y: account settings update * style: update my files dialog * fix: tests * chore: remove console.log() --------- Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
eba2c9a032
commit
2d62eca612
58 changed files with 1054 additions and 824 deletions
|
|
@ -1,36 +1,45 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link as LinkIcon } from 'lucide-react';
|
||||
import { Link as LinkIcon, TrashIcon } 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 OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { cn } from '~/utils';
|
||||
import {
|
||||
Button,
|
||||
Label,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TooltipAnchor,
|
||||
Skeleton,
|
||||
Spinner,
|
||||
OGDialog,
|
||||
OGDialogTrigger,
|
||||
} from '~/components';
|
||||
|
||||
function SharedLinkDeleteButton({
|
||||
shareId,
|
||||
setIsDeleting,
|
||||
}: {
|
||||
shareId: string;
|
||||
setIsDeleting: (isDeleting: boolean) => void;
|
||||
}) {
|
||||
function ShareLinkRow({ sharedLink }: { sharedLink: TSharedLink }) {
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const localize = useLocalize();
|
||||
|
||||
const { showToast } = useToastContext();
|
||||
const mutation = useDeleteSharedLinkMutation({
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: localize('com_ui_share_delete_error'),
|
||||
severity: NotificationSeverity.ERROR,
|
||||
showIcon: true,
|
||||
});
|
||||
setIsDeleting(false);
|
||||
},
|
||||
});
|
||||
|
||||
const handleDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
const confirmDelete = async (shareId: TSharedLink['shareId']) => {
|
||||
if (mutation.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -38,67 +47,78 @@ function SharedLinkDeleteButton({
|
|||
await mutation.mutateAsync({ shareId });
|
||||
setIsDeleting(false);
|
||||
};
|
||||
return (
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_delete')}
|
||||
id="delete-shared-link"
|
||||
aria-label="Delete shared link"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<TrashIcon className="size-4" />
|
||||
</TooltipAnchor>
|
||||
);
|
||||
}
|
||||
|
||||
function ShareLinkRow({ sharedLink }: { sharedLink: TSharedLink }) {
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={sharedLink.conversationId}
|
||||
className="border-b border-gray-200 text-sm font-normal dark:border-white/10"
|
||||
>
|
||||
<td
|
||||
className={cn(
|
||||
'flex items-center py-3 text-blue-800/70 dark:text-blue-500',
|
||||
isDeleting && 'opacity-50',
|
||||
)}
|
||||
>
|
||||
<Link to={`/share/${sharedLink.shareId}`} target="_blank" rel="noreferrer" className="flex">
|
||||
<LinkIcon className="mr-1 h-5 w-5" />
|
||||
<TableRow className={(cn(isDeleting && 'opacity-50'), 'hover:bg-transparent')}>
|
||||
<TableCell>
|
||||
<Link
|
||||
to={`/share/${sharedLink.shareId}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex items-center text-blue-500 hover:underline"
|
||||
>
|
||||
<LinkIcon className="mr-2 h-4 w-4" />
|
||||
{sharedLink.title}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<div className="flex justify-between">
|
||||
<div className={cn('flex justify-start dark:text-gray-200', isDeleting && 'opacity-50')}>
|
||||
{new Date(sharedLink.createdAt).toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-end gap-3 text-gray-400',
|
||||
isDeleting && 'opacity-50',
|
||||
)}
|
||||
>
|
||||
{sharedLink.conversationId && (
|
||||
<div className={cn('cursor-pointer', !isDeleting && 'hover:text-gray-300')}>
|
||||
<SharedLinkDeleteButton
|
||||
shareId={sharedLink.shareId}
|
||||
setIsDeleting={setIsDeleting}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{new Date(sharedLink.createdAt).toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{sharedLink.conversationId && (
|
||||
<OGDialog>
|
||||
<OGDialogTrigger asChild>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_delete')}
|
||||
render={
|
||||
<Button
|
||||
aria-label="Delete shared link"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8"
|
||||
>
|
||||
<TrashIcon className="size-4" />
|
||||
</Button>
|
||||
}
|
||||
></TooltipAnchor>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_ui_delete_conversation')}
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<>
|
||||
<div className="flex w-full flex-col items-center gap-2">
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label
|
||||
htmlFor="dialog-confirm-delete"
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{localize('com_ui_delete_confirm')} <strong>{sharedLink.title}</strong>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: () => confirmDelete(sharedLink.shareId),
|
||||
selectClasses:
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
</OGDialog>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
export default function ShareLinkTable({ className }: { className?: string }) {
|
||||
|
||||
export default function ShareLinkTable({ className }) {
|
||||
const localize = useLocalize();
|
||||
const { isAuthenticated } = useAuthContext();
|
||||
const [showLoading, setShowLoading] = useState(false);
|
||||
|
|
@ -114,15 +134,28 @@ export default function ShareLinkTable({ className }: { className?: string }) {
|
|||
});
|
||||
|
||||
const sharedLinks = useMemo(() => data?.pages.flatMap((page) => page.sharedLinks) || [], [data]);
|
||||
const classProp: { className?: string } = {
|
||||
className: 'p-1 hover:text-black dark:hover:text-white',
|
||||
};
|
||||
if (className) {
|
||||
classProp.className = className;
|
||||
}
|
||||
|
||||
const getRandomWidth = () => Math.floor(Math.random() * (400 - 170 + 1)) + 170;
|
||||
|
||||
const skeletons = Array.from({ length: 11 }, (_, index) => {
|
||||
const randomWidth = getRandomWidth();
|
||||
return (
|
||||
<div key={index} className="flex h-10 w-full items-center">
|
||||
<div className="flex w-[410px] items-center">
|
||||
<Skeleton className="h-4" style={{ width: `${randomWidth}px` }} />
|
||||
</div>
|
||||
<div className="flex flex-grow justify-center">
|
||||
<Skeleton className="h-4 w-28" />
|
||||
</div>
|
||||
<div className="mr-2 flex justify-end">
|
||||
<Skeleton className="h-4 w-12" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner className="m-1 mx-auto mb-4 h-4 w-4 text-black dark:text-white" />;
|
||||
return <div className="text-gray-300">{skeletons}</div>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
|
|
@ -132,35 +165,34 @@ export default function ShareLinkTable({ className }: { className?: string }) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
if (!sharedLinks || sharedLinks.length === 0) {
|
||||
|
||||
if (sharedLinks.length === 0) {
|
||||
return <div className="text-gray-300">{localize('com_nav_shared_links_empty')}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'grid w-full gap-2',
|
||||
'-mr-2 flex-1 flex-col overflow-y-auto pr-2 transition-opacity duration-500',
|
||||
'max-h-[350px]',
|
||||
'-mr-2 grid max-h-[350px] w-full flex-1 flex-col gap-2 overflow-y-auto pr-2 transition-opacity duration-500',
|
||||
className,
|
||||
)}
|
||||
ref={containerRef}
|
||||
>
|
||||
<table className="table-fixed text-left">
|
||||
<thead className="sticky top-0 bg-white dark:bg-gray-700">
|
||||
<tr className="border-b border-gray-200 text-sm font-semibold text-gray-500 dark:border-white/10 dark:text-gray-200">
|
||||
<th className="p-3">{localize('com_nav_shared_links_name')}</th>
|
||||
<th className="p-3">{localize('com_nav_shared_links_date_shared')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{localize('com_nav_shared_links_name')}</TableHead>
|
||||
<TableHead>{localize('com_nav_shared_links_date_shared')}</TableHead>
|
||||
<TableHead className="text-right">{localize('com_assistants_actions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{sharedLinks.map((sharedLink) => (
|
||||
<ShareLinkRow key={sharedLink.shareId} sharedLink={sharedLink} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{(isFetchingNextPage || showLoading) && (
|
||||
<Spinner className={cn('m-1 mx-auto mb-4 h-4 w-4 text-black dark:text-white')} />
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{(isFetchingNextPage || showLoading) && <Spinner className="mx-auto my-4" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue