Skip to content

Commit 00c4240

Browse files
authored
🎨 refactor: Enhance UI Consistency, Accessibility & Localization (danny-avila#7788)
1 parent 83ac304 commit 00c4240

File tree

8 files changed

+104
-127
lines changed

8 files changed

+104
-127
lines changed

client/src/components/Bookmarks/DeleteBookmarkButton.tsx

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { useCallback, useState } from 'react';
22
import type { FC } from 'react';
3-
import { Label, OGDialog, OGDialogTrigger, TooltipAnchor } from '~/components/ui';
3+
import { Button, TrashIcon, Label, OGDialog, OGDialogTrigger, TooltipAnchor } from '~/components';
44
import { useDeleteConversationTagMutation } from '~/data-provider';
55
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
66
import { NotificationSeverity } from '~/common';
77
import { useToastContext } from '~/Providers';
8-
import { TrashIcon } from '~/components/svg';
98
import { useLocalize } from '~/hooks';
109

1110
const DeleteBookmarkButton: FC<{
@@ -36,31 +35,26 @@ const DeleteBookmarkButton: FC<{
3635
await deleteBookmarkMutation.mutateAsync(bookmark);
3736
}, [bookmark, deleteBookmarkMutation]);
3837

39-
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
40-
if (event.key === 'Enter' || event.key === ' ') {
41-
event.preventDefault();
42-
event.stopPropagation();
43-
setOpen(!open);
44-
}
45-
};
46-
4738
return (
4839
<>
4940
<OGDialog open={open} onOpenChange={setOpen}>
5041
<OGDialogTrigger asChild>
5142
<TooltipAnchor
52-
role="button"
53-
aria-label={localize('com_ui_bookmarks_delete')}
5443
description={localize('com_ui_delete')}
55-
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
56-
tabIndex={tabIndex}
57-
onFocus={onFocus}
58-
onBlur={onBlur}
59-
onClick={() => setOpen(!open)}
60-
onKeyDown={handleKeyDown}
61-
>
62-
<TrashIcon className="size-4" />
63-
</TooltipAnchor>
44+
render={
45+
<Button
46+
variant="ghost"
47+
aria-label={localize('com_ui_bookmarks_delete')}
48+
tabIndex={tabIndex}
49+
onFocus={onFocus}
50+
onBlur={onBlur}
51+
onClick={() => setOpen(!open)}
52+
className="h-8 w-8 p-0"
53+
>
54+
<TrashIcon />
55+
</Button>
56+
}
57+
/>
6458
</OGDialogTrigger>
6559
<OGDialogTemplate
6660
showCloseButton={false}

client/src/components/Bookmarks/EditBookmarkButton.tsx

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { useState } from 'react';
22
import type { FC } from 'react';
33
import type { TConversationTag } from 'librechat-data-provider';
4-
import { TooltipAnchor, OGDialogTrigger } from '~/components/ui';
4+
import { TooltipAnchor, OGDialogTrigger, EditIcon, Button } from '~/components';
55
import BookmarkEditDialog from './BookmarkEditDialog';
6-
import { EditIcon } from '~/components/svg';
76
import { useLocalize } from '~/hooks';
87

98
const EditBookmarkButton: FC<{
@@ -15,12 +14,6 @@ const EditBookmarkButton: FC<{
1514
const localize = useLocalize();
1615
const [open, setOpen] = useState(false);
1716

18-
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
19-
if (event.key === 'Enter' || event.key === ' ') {
20-
setOpen(!open);
21-
}
22-
};
23-
2417
return (
2518
<BookmarkEditDialog
2619
context="EditBookmarkButton"
@@ -30,18 +23,21 @@ const EditBookmarkButton: FC<{
3023
>
3124
<OGDialogTrigger asChild>
3225
<TooltipAnchor
33-
role="button"
34-
aria-label={localize('com_ui_bookmarks_edit')}
3526
description={localize('com_ui_edit')}
36-
tabIndex={tabIndex}
37-
onFocus={onFocus}
38-
onBlur={onBlur}
39-
onClick={() => setOpen(!open)}
40-
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
41-
onKeyDown={handleKeyDown}
42-
>
43-
<EditIcon />
44-
</TooltipAnchor>
27+
render={
28+
<Button
29+
variant="ghost"
30+
aria-label={localize('com_ui_bookmarks_edit')}
31+
tabIndex={tabIndex}
32+
onFocus={onFocus}
33+
onBlur={onBlur}
34+
onClick={() => setOpen(!open)}
35+
className="h-8 w-8 p-0"
36+
>
37+
<EditIcon />
38+
</Button>
39+
}
40+
/>
4541
</OGDialogTrigger>
4642
</BookmarkEditDialog>
4743
);
Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ChevronLeft, ChevronRight } from 'lucide-react';
12
import type { TMessageProps } from '~/common';
23
import { cn } from '~/utils';
34

@@ -22,57 +23,46 @@ export default function SiblingSwitch({
2223
setSiblingIdx && setSiblingIdx(siblingIdx + 1);
2324
};
2425

26+
const buttonStyle = cn(
27+
'hover-button rounded-lg p-1.5 text-text-secondary-alt transition-colors duration-200',
28+
'hover:text-text-primary hover:bg-surface-hover',
29+
'md:group-hover:visible md:group-focus-within:visible md:group-[.final-completion]:visible',
30+
'focus-visible:ring-2 focus-visible:ring-black dark:focus-visible:ring-white focus-visible:outline-none',
31+
);
32+
2533
return siblingCount > 1 ? (
26-
<div className="visible flex items-center justify-center gap-1 self-center pt-0 text-xs">
34+
<nav
35+
className="visible flex items-center justify-center gap-2 self-center pt-0 text-xs"
36+
aria-label="Sibling message navigation"
37+
>
2738
<button
28-
className={cn(
29-
'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',
30-
)}
39+
className={buttonStyle}
3140
type="button"
3241
onClick={previous}
3342
disabled={siblingIdx == 0}
43+
aria-label="Previous sibling message"
44+
aria-disabled={siblingIdx == 0}
3445
>
35-
<svg
36-
stroke="currentColor"
37-
fill="none"
38-
strokeWidth="1.5"
39-
viewBox="0 0 24 24"
40-
strokeLinecap="round"
41-
strokeLinejoin="round"
42-
className="h-4 w-4"
43-
height="1em"
44-
width="1em"
45-
xmlns="http://www.w3.org/2000/svg"
46-
>
47-
<polyline points="15 18 9 12 15 6" />
48-
</svg>
46+
<ChevronLeft size="19" aria-hidden="true" />
4947
</button>
50-
<span className="flex-shrink-0 flex-grow tabular-nums">
48+
<span
49+
className="flex-shrink-0 flex-grow tabular-nums"
50+
aria-live="polite"
51+
aria-atomic="true"
52+
role="status"
53+
>
5154
{siblingIdx + 1} / {siblingCount}
5255
</span>
5356
<button
54-
className={cn(
55-
'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',
56-
)}
57+
className={buttonStyle}
5758
type="button"
5859
onClick={next}
5960
disabled={siblingIdx == siblingCount - 1}
61+
aria-label="Next sibling message"
62+
aria-disabled={siblingIdx == siblingCount - 1}
6063
>
61-
<svg
62-
stroke="currentColor"
63-
fill="none"
64-
strokeWidth="1.5"
65-
viewBox="0 0 24 24"
66-
strokeLinecap="round"
67-
strokeLinejoin="round"
68-
className="h-4 w-4"
69-
height="1em"
70-
width="1em"
71-
xmlns="http://www.w3.org/2000/svg"
72-
>
73-
<polyline points="9 18 15 12 9 6" />
74-
</svg>
64+
<ChevronRight size="19" aria-hidden="true" />
7565
</button>
76-
</div>
66+
</nav>
7767
) : null;
7868
}

client/src/components/SidePanel/Bookmarks/BookmarkTableRow.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ const BookmarkTableRow: React.FC<BookmarkTableRowProps> = ({ row, moveRow, posit
3030
mutation.mutate(
3131
{ ...row, position: item.index },
3232
{
33+
onSuccess: () => {
34+
showToast({
35+
message: localize('com_ui_bookmarks_update_success'),
36+
severity: NotificationSeverity.SUCCESS,
37+
});
38+
},
3339
onError: () => {
3440
showToast({
3541
message: localize('com_ui_bookmarks_update_error'),
@@ -44,7 +50,9 @@ const BookmarkTableRow: React.FC<BookmarkTableRowProps> = ({ row, moveRow, posit
4450
accept: 'bookmark',
4551
drop: handleDrop,
4652
hover(item: DragItem) {
47-
if (!ref.current || item.index === position) {return;}
53+
if (!ref.current || item.index === position) {
54+
return;
55+
}
4856
moveRow(item.index, position);
4957
item.index = position;
5058
},

client/src/components/SidePanel/Memories/MemoryEditDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export default function MemoryEditDialog({
117117
<div>
118118
{memory.tokenCount.toLocaleString()}
119119
{memData?.tokenLimit && ` / ${memData.tokenLimit.toLocaleString()}`}{' '}
120-
{localize('com_ui_tokens')}
120+
{localize(memory.tokenCount === 1 ? 'com_ui_token' : 'com_ui_tokens')}
121121
</div>
122122
)}
123123
</div>

client/src/components/SidePanel/Memories/MemoryViewer.tsx

Lines changed: 32 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { matchSorter } from 'match-sorter';
55
import { SystemRoles, PermissionTypes, Permissions } from 'librechat-data-provider';
66
import type { TUserMemory } from 'librechat-data-provider';
77
import {
8+
Spinner,
9+
EditIcon,
10+
TrashIcon,
811
Table,
912
Input,
1013
Label,
@@ -18,7 +21,7 @@ import {
1821
TableHeader,
1922
TooltipAnchor,
2023
OGDialogTrigger,
21-
} from '~/components/ui';
24+
} from '~/components';
2225
import {
2326
useGetUserQuery,
2427
useMemoriesQuery,
@@ -27,10 +30,8 @@ import {
2730
} from '~/data-provider';
2831
import { useLocalize, useAuthContext, useHasAccess } from '~/hooks';
2932
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
30-
import { EditIcon, TrashIcon } from '~/components/svg';
3133
import MemoryCreateDialog from './MemoryCreateDialog';
3234
import MemoryEditDialog from './MemoryEditDialog';
33-
import Spinner from '~/components/svg/Spinner';
3435
import { useToastContext } from '~/Providers';
3536
import AdminSettings from './AdminSettings';
3637

@@ -121,13 +122,6 @@ export default function MemoryViewer() {
121122
const [open, setOpen] = useState(false);
122123
const triggerRef = useRef<HTMLDivElement>(null);
123124

124-
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
125-
if (event.key === 'Enter' || event.key === ' ') {
126-
event.preventDefault();
127-
setOpen(!open);
128-
}
129-
};
130-
131125
// Only show edit button if user has UPDATE permission
132126
if (!hasUpdateAccess) {
133127
return null;
@@ -142,17 +136,18 @@ export default function MemoryViewer() {
142136
>
143137
<OGDialogTrigger asChild>
144138
<TooltipAnchor
145-
ref={triggerRef}
146-
role="button"
147-
aria-label={localize('com_ui_edit')}
148-
description={localize('com_ui_edit')}
149-
tabIndex={0}
150-
onClick={() => setOpen(!open)}
151-
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
152-
onKeyDown={handleKeyDown}
153-
>
154-
<EditIcon />
155-
</TooltipAnchor>
139+
description={localize('com_ui_edit_memory')}
140+
render={
141+
<Button
142+
variant="ghost"
143+
aria-label={localize('com_ui_bookmarks_edit')}
144+
onClick={() => setOpen(!open)}
145+
className="h-8 w-8 p-0"
146+
>
147+
<EditIcon />
148+
</Button>
149+
}
150+
/>
156151
</OGDialogTrigger>
157152
</MemoryEditDialog>
158153
);
@@ -161,14 +156,6 @@ export default function MemoryViewer() {
161156
const DeleteMemoryButton = ({ memory }: { memory: TUserMemory }) => {
162157
const [open, setOpen] = useState(false);
163158

164-
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
165-
if (event.key === 'Enter' || event.key === ' ') {
166-
event.preventDefault();
167-
event.stopPropagation();
168-
setOpen(!open);
169-
}
170-
};
171-
172159
if (!hasUpdateAccess) {
173160
return null;
174161
}
@@ -196,20 +183,22 @@ export default function MemoryViewer() {
196183
<OGDialog open={open} onOpenChange={setOpen}>
197184
<OGDialogTrigger asChild>
198185
<TooltipAnchor
199-
role="button"
200-
aria-label={localize('com_ui_delete')}
201-
description={localize('com_ui_delete')}
202-
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
203-
tabIndex={0}
204-
onClick={() => setOpen(!open)}
205-
onKeyDown={handleKeyDown}
206-
>
207-
{deletingKey === memory.key ? (
208-
<Spinner className="size-4 animate-spin" />
209-
) : (
210-
<TrashIcon className="size-4" />
211-
)}
212-
</TooltipAnchor>
186+
description={localize('com_ui_delete_memory')}
187+
render={
188+
<Button
189+
variant="ghost"
190+
aria-label={localize('com_ui_delete')}
191+
onClick={() => setOpen(!open)}
192+
className="h-8 w-8 p-0"
193+
>
194+
{deletingKey === memory.key ? (
195+
<Spinner className="size-4 animate-spin" />
196+
) : (
197+
<TrashIcon className="size-4" />
198+
)}
199+
</Button>
200+
}
201+
/>
213202
</OGDialogTrigger>
214203
<OGDialogTemplate
215204
showCloseButton={false}

client/src/components/ui/OGDialogTemplate.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
OGDialogDescription,
99
} from './OriginalDialog';
1010
import { useLocalize } from '~/hooks';
11+
import { Button } from './Button';
1112
import { Spinner } from '../svg';
1213
import { cn } from '~/utils/';
1314

@@ -53,7 +54,6 @@ const OGDialogTemplate = forwardRef((props: DialogTemplateProps, ref: Ref<HTMLDi
5354
showCancelButton = true,
5455
} = props;
5556
const { selectHandler, selectClasses, selectText, isLoading } = selection || {};
56-
const Cancel = localize('com_ui_cancel');
5757

5858
const defaultSelect =
5959
'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
8383
) : null}
8484
</div>
8585
<div className="flex h-auto gap-3 max-sm:w-full max-sm:flex-col sm:flex-row">
86-
{buttons != null ? buttons : null}
8786
{showCancelButton && (
88-
<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">
89-
{Cancel}
87+
<OGDialogClose asChild>
88+
<Button variant="outline">{localize('com_ui_cancel')}</Button>
9089
</OGDialogClose>
9190
)}
91+
{buttons != null ? buttons : null}
9292
{selection ? (
9393
<OGDialogClose
9494
onClick={selectHandler}

0 commit comments

Comments
 (0)