mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
🎨 refactor: Enhance UI Consistency, Accessibility & Localization (#7788)
This commit is contained in:
parent
9bb9aba8ec
commit
b0054c775a
8 changed files with 104 additions and 127 deletions
|
|
@ -1,11 +1,10 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { Label, OGDialog, OGDialogTrigger, TooltipAnchor } from '~/components/ui';
|
||||
import { Button, TrashIcon, Label, OGDialog, OGDialogTrigger, TooltipAnchor } from '~/components';
|
||||
import { useDeleteConversationTagMutation } from '~/data-provider';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { TrashIcon } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const DeleteBookmarkButton: FC<{
|
||||
|
|
@ -36,31 +35,26 @@ const DeleteBookmarkButton: FC<{
|
|||
await deleteBookmarkMutation.mutateAsync(bookmark);
|
||||
}, [bookmark, deleteBookmarkMutation]);
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setOpen(!open);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<OGDialog open={open} onOpenChange={setOpen}>
|
||||
<OGDialogTrigger asChild>
|
||||
<TooltipAnchor
|
||||
role="button"
|
||||
aria-label={localize('com_ui_bookmarks_delete')}
|
||||
description={localize('com_ui_delete')}
|
||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-label={localize('com_ui_bookmarks_delete')}
|
||||
tabIndex={tabIndex}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onClick={() => setOpen(!open)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<TrashIcon className="size-4" />
|
||||
</TooltipAnchor>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import type { TConversationTag } from 'librechat-data-provider';
|
||||
import { TooltipAnchor, OGDialogTrigger } from '~/components/ui';
|
||||
import { TooltipAnchor, OGDialogTrigger, EditIcon, Button } from '~/components';
|
||||
import BookmarkEditDialog from './BookmarkEditDialog';
|
||||
import { EditIcon } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const EditBookmarkButton: FC<{
|
||||
|
|
@ -15,12 +14,6 @@ const EditBookmarkButton: FC<{
|
|||
const localize = useLocalize();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
setOpen(!open);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BookmarkEditDialog
|
||||
context="EditBookmarkButton"
|
||||
|
|
@ -30,18 +23,21 @@ const EditBookmarkButton: FC<{
|
|||
>
|
||||
<OGDialogTrigger asChild>
|
||||
<TooltipAnchor
|
||||
role="button"
|
||||
aria-label={localize('com_ui_bookmarks_edit')}
|
||||
description={localize('com_ui_edit')}
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-label={localize('com_ui_bookmarks_edit')}
|
||||
tabIndex={tabIndex}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<EditIcon />
|
||||
</TooltipAnchor>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</OGDialogTrigger>
|
||||
</BookmarkEditDialog>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -22,57 +23,46 @@ export default function SiblingSwitch({
|
|||
setSiblingIdx && setSiblingIdx(siblingIdx + 1);
|
||||
};
|
||||
|
||||
const buttonStyle = cn(
|
||||
'hover-button rounded-lg p-1.5 text-text-secondary-alt transition-colors duration-200',
|
||||
'hover:text-text-primary hover:bg-surface-hover',
|
||||
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
|
||||
'focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:outline-none',
|
||||
);
|
||||
|
||||
return siblingCount > 1 ? (
|
||||
<div className="visible flex items-center justify-center gap-1 self-center pt-0 text-xs">
|
||||
<nav
|
||||
className="visible flex items-center justify-center gap-2 self-center pt-0 text-xs"
|
||||
aria-label="Sibling message navigation"
|
||||
>
|
||||
<button
|
||||
className={cn(
|
||||
'hover-button rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-500 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
)}
|
||||
className={buttonStyle}
|
||||
type="button"
|
||||
onClick={previous}
|
||||
disabled={siblingIdx == 0}
|
||||
aria-label="Previous sibling message"
|
||||
aria-disabled={siblingIdx == 0}
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
<ChevronLeft size="19" aria-hidden="true" />
|
||||
</button>
|
||||
<span className="flex-shrink-0 flex-grow tabular-nums">
|
||||
<span
|
||||
className="flex-shrink-0 flex-grow tabular-nums"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
role="status"
|
||||
>
|
||||
{siblingIdx + 1} / {siblingCount}
|
||||
</span>
|
||||
<button
|
||||
className={cn(
|
||||
'hover-button rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-500 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
)}
|
||||
className={buttonStyle}
|
||||
type="button"
|
||||
onClick={next}
|
||||
disabled={siblingIdx == siblingCount - 1}
|
||||
aria-label="Next sibling message"
|
||||
aria-disabled={siblingIdx == siblingCount - 1}
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polyline points="9 18 15 12 9 6" />
|
||||
</svg>
|
||||
<ChevronRight size="19" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
) : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ const BookmarkTableRow: React.FC<BookmarkTableRowProps> = ({ row, moveRow, posit
|
|||
mutation.mutate(
|
||||
{ ...row, position: item.index },
|
||||
{
|
||||
onSuccess: () => {
|
||||
showToast({
|
||||
message: localize('com_ui_bookmarks_update_success'),
|
||||
severity: NotificationSeverity.SUCCESS,
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: localize('com_ui_bookmarks_update_error'),
|
||||
|
|
@ -44,7 +50,9 @@ const BookmarkTableRow: React.FC<BookmarkTableRowProps> = ({ row, moveRow, posit
|
|||
accept: 'bookmark',
|
||||
drop: handleDrop,
|
||||
hover(item: DragItem) {
|
||||
if (!ref.current || item.index === position) {return;}
|
||||
if (!ref.current || item.index === position) {
|
||||
return;
|
||||
}
|
||||
moveRow(item.index, position);
|
||||
item.index = position;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ export default function MemoryEditDialog({
|
|||
<div>
|
||||
{memory.tokenCount.toLocaleString()}
|
||||
{memData?.tokenLimit && ` / ${memData.tokenLimit.toLocaleString()}`}{' '}
|
||||
{localize('com_ui_tokens')}
|
||||
{localize(memory.tokenCount === 1 ? 'com_ui_token' : 'com_ui_tokens')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { matchSorter } from 'match-sorter';
|
|||
import { SystemRoles, PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import type { TUserMemory } from 'librechat-data-provider';
|
||||
import {
|
||||
Spinner,
|
||||
EditIcon,
|
||||
TrashIcon,
|
||||
Table,
|
||||
Input,
|
||||
Label,
|
||||
|
|
@ -18,7 +21,7 @@ import {
|
|||
TableHeader,
|
||||
TooltipAnchor,
|
||||
OGDialogTrigger,
|
||||
} from '~/components/ui';
|
||||
} from '~/components';
|
||||
import {
|
||||
useGetUserQuery,
|
||||
useMemoriesQuery,
|
||||
|
|
@ -27,10 +30,8 @@ import {
|
|||
} from '~/data-provider';
|
||||
import { useLocalize, useAuthContext, useHasAccess } from '~/hooks';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { EditIcon, TrashIcon } from '~/components/svg';
|
||||
import MemoryCreateDialog from './MemoryCreateDialog';
|
||||
import MemoryEditDialog from './MemoryEditDialog';
|
||||
import Spinner from '~/components/svg/Spinner';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import AdminSettings from './AdminSettings';
|
||||
|
||||
|
|
@ -121,13 +122,6 @@ export default function MemoryViewer() {
|
|||
const [open, setOpen] = useState(false);
|
||||
const triggerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
setOpen(!open);
|
||||
}
|
||||
};
|
||||
|
||||
// Only show edit button if user has UPDATE permission
|
||||
if (!hasUpdateAccess) {
|
||||
return null;
|
||||
|
|
@ -142,17 +136,18 @@ export default function MemoryViewer() {
|
|||
>
|
||||
<OGDialogTrigger asChild>
|
||||
<TooltipAnchor
|
||||
ref={triggerRef}
|
||||
role="button"
|
||||
aria-label={localize('com_ui_edit')}
|
||||
description={localize('com_ui_edit')}
|
||||
tabIndex={0}
|
||||
description={localize('com_ui_edit_memory')}
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-label={localize('com_ui_bookmarks_edit')}
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<EditIcon />
|
||||
</TooltipAnchor>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</OGDialogTrigger>
|
||||
</MemoryEditDialog>
|
||||
);
|
||||
|
|
@ -161,14 +156,6 @@ export default function MemoryViewer() {
|
|||
const DeleteMemoryButton = ({ memory }: { memory: TUserMemory }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setOpen(!open);
|
||||
}
|
||||
};
|
||||
|
||||
if (!hasUpdateAccess) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -196,20 +183,22 @@ export default function MemoryViewer() {
|
|||
<OGDialog open={open} onOpenChange={setOpen}>
|
||||
<OGDialogTrigger asChild>
|
||||
<TooltipAnchor
|
||||
role="button"
|
||||
description={localize('com_ui_delete_memory')}
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
aria-label={localize('com_ui_delete')}
|
||||
description={localize('com_ui_delete')}
|
||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
tabIndex={0}
|
||||
onClick={() => setOpen(!open)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
{deletingKey === memory.key ? (
|
||||
<Spinner className="size-4 animate-spin" />
|
||||
) : (
|
||||
<TrashIcon className="size-4" />
|
||||
)}
|
||||
</TooltipAnchor>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
OGDialogDescription,
|
||||
} from './OriginalDialog';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { Button } from './Button';
|
||||
import { Spinner } from '../svg';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
|
|
@ -53,7 +54,6 @@ const OGDialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDi
|
|||
showCancelButton = true,
|
||||
} = props;
|
||||
const { selectHandler, selectClasses, selectText, isLoading } = selection || {};
|
||||
const Cancel = localize('com_ui_cancel');
|
||||
|
||||
const defaultSelect =
|
||||
'bg-gray-800 text-white transition-colors hover:bg-gray-700 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-200 dark:text-gray-800 dark:hover:bg-gray-200';
|
||||
|
|
@ -83,12 +83,12 @@ const OGDialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDi
|
|||
) : null}
|
||||
</div>
|
||||
<div className="flex h-auto gap-3 max-sm:w-full max-sm:flex-col sm:flex-row">
|
||||
{buttons != null ? buttons : null}
|
||||
{showCancelButton && (
|
||||
<OGDialogClose className="btn btn-neutral border-token-border-light relative justify-center rounded-lg text-sm ring-offset-2 focus:ring-2 focus:ring-black dark:ring-offset-0 max-sm:order-last max-sm:w-full sm:order-first">
|
||||
{Cancel}
|
||||
<OGDialogClose asChild>
|
||||
<Button variant="outline">{localize('com_ui_cancel')}</Button>
|
||||
</OGDialogClose>
|
||||
)}
|
||||
{buttons != null ? buttons : null}
|
||||
{selection ? (
|
||||
<OGDialogClose
|
||||
onClick={selectHandler}
|
||||
|
|
|
|||
|
|
@ -798,6 +798,7 @@
|
|||
"com_ui_usage": "Usage",
|
||||
"com_ui_current": "Current",
|
||||
"com_ui_tokens": "tokens",
|
||||
"com_ui_token": "token",
|
||||
"com_ui_mention": "Mention an endpoint, assistant, or preset to quickly switch to it",
|
||||
"com_ui_min_tags": "Cannot remove more values, a minimum of {{0}} are required.",
|
||||
"com_ui_misc": "Misc.",
|
||||
|
|
@ -1006,4 +1007,3 @@
|
|||
"com_ui_memory_created": "Memory created successfully",
|
||||
"com_ui_memory_key_exists": "A memory with this key already exists. Please use a different key."
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue