mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-30 06:15:18 +01:00
🔗 feat: More Accessible Link Behaviors and Minor UI Improvements (#11549)
Some checks are pending
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Some checks are pending
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
* fix: accessibility issues with links and link descriptions + minor ui tweaks * fix: link accessibility in archived chats table * fix: remove open in new tab behavior for other footer links * chore: remove unused translation string * style: formatting * refactor: rename searchState to searchStore for clarity * chore: Reorganize imports and state variables in SharedLinks * chore: re-organize imports/hooks --------- Co-authored-by: Danny Avila <danacordially@gmail.com> Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
95a234fb83
commit
13cea97c9b
4 changed files with 129 additions and 105 deletions
|
|
@ -13,30 +13,14 @@ export default function Footer({ className }: { className?: string }) {
|
|||
const termsOfService = config?.interface?.termsOfService;
|
||||
|
||||
const privacyPolicyRender = privacyPolicy?.externalUrl != null && (
|
||||
<a
|
||||
className="text-text-secondary underline"
|
||||
href={privacyPolicy.externalUrl}
|
||||
target={privacyPolicy.openNewTab === true ? '_blank' : undefined}
|
||||
rel="noreferrer"
|
||||
>
|
||||
<a className="text-text-secondary underline" href={privacyPolicy.externalUrl} rel="noreferrer">
|
||||
{localize('com_ui_privacy_policy')}
|
||||
{privacyPolicy.openNewTab === true && (
|
||||
<span className="sr-only">{' ' + localize('com_ui_opens_new_tab')}</span>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
|
||||
const termsOfServiceRender = termsOfService?.externalUrl != null && (
|
||||
<a
|
||||
className="text-text-secondary underline"
|
||||
href={termsOfService.externalUrl}
|
||||
target={termsOfService.openNewTab === true ? '_blank' : undefined}
|
||||
rel="noreferrer"
|
||||
>
|
||||
<a className="text-text-secondary underline" href={termsOfService.externalUrl} rel="noreferrer">
|
||||
{localize('com_ui_terms_of_service')}
|
||||
{termsOfService.openNewTab === true && (
|
||||
<span className="sr-only">{' ' + localize('com_ui_opens_new_tab')}</span>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
|
||||
|
|
@ -67,12 +51,10 @@ export default function Footer({ className }: { className?: string }) {
|
|||
<a
|
||||
className="text-text-secondary underline"
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
<span className="sr-only">{' ' + localize('com_ui_opens_new_tab')}</span>
|
||||
</a>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,33 +4,33 @@ import debounce from 'lodash/debounce';
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
TrashIcon,
|
||||
MessageSquare,
|
||||
ArrowUpDown,
|
||||
ArrowUp,
|
||||
TrashIcon,
|
||||
ArrowDown,
|
||||
ArrowUpDown,
|
||||
ExternalLink,
|
||||
MessageSquare,
|
||||
} from 'lucide-react';
|
||||
import type { SharedLinkItem, SharedLinksListParams } from 'librechat-data-provider';
|
||||
import type { TranslationKeys } from '~/hooks';
|
||||
import {
|
||||
Label,
|
||||
Button,
|
||||
Spinner,
|
||||
OGDialog,
|
||||
useToastContext,
|
||||
OGDialogTemplate,
|
||||
OGDialogTrigger,
|
||||
OGDialogContent,
|
||||
DataTable,
|
||||
useMediaQuery,
|
||||
OGDialogHeader,
|
||||
OGDialogTitle,
|
||||
TooltipAnchor,
|
||||
DataTable,
|
||||
Spinner,
|
||||
Button,
|
||||
Label,
|
||||
OGDialogHeader,
|
||||
OGDialogTrigger,
|
||||
OGDialogContent,
|
||||
useToastContext,
|
||||
OGDialogTemplate,
|
||||
} from '@librechat/client';
|
||||
import type { SharedLinkItem, SharedLinksListParams } from 'librechat-data-provider';
|
||||
import type { TranslationKeys } from '~/hooks';
|
||||
import { useDeleteSharedLinkMutation, useSharedLinksQuery } from '~/data-provider';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { formatDate } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -47,12 +47,12 @@ const DEFAULT_PARAMS: SharedLinksListParams = {
|
|||
export default function SharedLinks() {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
const isSearchEnabled = useRecoilValue(store.search);
|
||||
const [queryParams, setQueryParams] = useState<SharedLinksListParams>(DEFAULT_PARAMS);
|
||||
const [deleteRow, setDeleteRow] = useState<SharedLinkItem | null>(null);
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const searchStore = useRecoilValue(store.search);
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
const [deleteRow, setDeleteRow] = useState<SharedLinkItem | null>(null);
|
||||
const [queryParams, setQueryParams] = useState<SharedLinksListParams>(DEFAULT_PARAMS);
|
||||
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isLoading } =
|
||||
useSharedLinksQuery(queryParams, {
|
||||
|
|
@ -173,17 +173,23 @@ export default function SharedLinks() {
|
|||
ariaSort = 'ascending';
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||
aria-sort={ariaSort}
|
||||
aria-label={localize('com_ui_name_sort')}
|
||||
aria-current={sortState ? 'true' : 'false'}
|
||||
>
|
||||
{localize('com_ui_name')}
|
||||
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_name_sort')}
|
||||
side="top"
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||
aria-sort={ariaSort}
|
||||
aria-label={localize('com_ui_name_sort')}
|
||||
aria-current={sortState ? 'true' : 'false'}
|
||||
>
|
||||
{localize('com_ui_name')}
|
||||
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
|
|
@ -207,7 +213,7 @@ export default function SharedLinks() {
|
|||
);
|
||||
},
|
||||
meta: {
|
||||
size: '35%',
|
||||
size: '32%',
|
||||
mobileSize: '50%',
|
||||
},
|
||||
},
|
||||
|
|
@ -225,17 +231,23 @@ export default function SharedLinks() {
|
|||
ariaSort = 'ascending';
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||
aria-sort={ariaSort}
|
||||
aria-label={localize('com_ui_creation_date_sort' as TranslationKeys)}
|
||||
aria-current={sortState ? 'true' : 'false'}
|
||||
>
|
||||
{localize('com_ui_date')}
|
||||
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_date_sort')}
|
||||
side="top"
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||
aria-sort={ariaSort}
|
||||
aria-label={localize('com_ui_date_sort')}
|
||||
aria-current={sortState ? 'true' : 'false'}
|
||||
>
|
||||
{localize('com_ui_date')}
|
||||
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen),
|
||||
|
|
@ -247,7 +259,7 @@ export default function SharedLinks() {
|
|||
{
|
||||
accessorKey: 'actions',
|
||||
header: () => (
|
||||
<Label className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm">
|
||||
<Label className="px-2 py-0 text-xs sm:px-2 sm:py-2 sm:text-sm">
|
||||
{localize('com_assistants_actions')}
|
||||
</Label>
|
||||
),
|
||||
|
|
@ -330,7 +342,7 @@ export default function SharedLinks() {
|
|||
onFilterChange={debouncedFilterChange}
|
||||
filterValue={queryParams.search}
|
||||
isLoading={isLoading}
|
||||
enableSearch={isSearchEnabled}
|
||||
enableSearch={searchStore.enabled === true}
|
||||
/>
|
||||
</OGDialogContent>
|
||||
</OGDialog>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,18 @@ import { useState, useCallback, useMemo, useEffect } from 'react';
|
|||
import { Trans } from 'react-i18next';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { TrashIcon, ArchiveRestore, ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
ArrowUp,
|
||||
TrashIcon,
|
||||
ArrowDown,
|
||||
ArrowUpDown,
|
||||
ExternalLink,
|
||||
ArchiveRestore,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
Button,
|
||||
Label,
|
||||
Button,
|
||||
Spinner,
|
||||
OGDialog,
|
||||
DataTable,
|
||||
|
|
@ -17,7 +25,6 @@ import {
|
|||
OGDialogContent,
|
||||
} from '@librechat/client';
|
||||
import type { ConversationListParams, TConversation } from 'librechat-data-provider';
|
||||
import type { TranslationKeys } from '~/hooks';
|
||||
import {
|
||||
useConversationsInfiniteQuery,
|
||||
useDeleteConversationMutation,
|
||||
|
|
@ -42,10 +49,10 @@ export default function ArchivedChatsTable({
|
|||
onOpenChange: (isOpen: boolean) => void;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
const { showToast } = useToastContext();
|
||||
const searchState = useRecoilValue(store.search);
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
const [queryParams, setQueryParams] = useState<ConversationListParams>(DEFAULT_PARAMS);
|
||||
const [deleteConversation, setDeleteConversation] = useState<TConversation | null>(null);
|
||||
|
||||
|
|
@ -138,35 +145,50 @@ export default function ArchivedChatsTable({
|
|||
ariaSort = 'ascending';
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||
aria-sort={ariaSort}
|
||||
aria-label={localize('com_nav_archive_name_sort' as TranslationKeys)}
|
||||
aria-current={sortState ? 'true' : 'false'}
|
||||
>
|
||||
{localize('com_nav_archive_name')}
|
||||
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_name_sort')}
|
||||
side="top"
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||
aria-sort={ariaSort}
|
||||
aria-label={localize('com_ui_name_sort')}
|
||||
aria-current={sortState ? 'true' : 'false'}
|
||||
>
|
||||
{localize('com_nav_archive_name')}
|
||||
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const { conversationId, title } = row.original;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 truncate rounded-sm"
|
||||
onClick={() => window.open(`/c/${conversationId}`, '_blank')}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<MinimalIcon
|
||||
endpoint={row.original.endpoint}
|
||||
size={28}
|
||||
isCreatedByUser={false}
|
||||
iconClassName="size-4"
|
||||
/>
|
||||
<span className="underline">{title}</span>
|
||||
</button>
|
||||
<Link
|
||||
to={`/c/${conversationId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-center gap-1 truncate rounded-sm text-blue-600 underline decoration-1 underline-offset-2 hover:decoration-2 focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
title={title}
|
||||
aria-label={localize('com_ui_open_archived_chat_new_tab_title', { title })}
|
||||
>
|
||||
<span className="truncate">{title}</span>
|
||||
<ExternalLink
|
||||
className="size-3 flex-shrink-0 opacity-70 group-hover:opacity-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
meta: {
|
||||
|
|
@ -188,17 +210,23 @@ export default function ArchivedChatsTable({
|
|||
ariaSort = 'ascending';
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||
aria-sort={ariaSort}
|
||||
aria-label={localize('com_nav_archive_created_at_sort' as TranslationKeys)}
|
||||
aria-current={sortState ? 'true' : 'false'}
|
||||
>
|
||||
{localize('com_nav_archive_created_at')}
|
||||
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_date_sort')}
|
||||
side="top"
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||
aria-sort={ariaSort}
|
||||
aria-label={localize('com_ui_date_sort')}
|
||||
aria-current={sortState ? 'true' : 'false'}
|
||||
>
|
||||
{localize('com_nav_archive_created_at')}
|
||||
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => formatDate(row.original.createdAt?.toString() ?? '', isSmallScreen),
|
||||
|
|
@ -219,7 +247,7 @@ export default function ArchivedChatsTable({
|
|||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_unarchive')}
|
||||
description={localize('com_ui_unarchive_conversation')}
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -230,8 +258,8 @@ export default function ArchivedChatsTable({
|
|||
isArchived: false,
|
||||
})
|
||||
}
|
||||
title={localize('com_ui_unarchive')}
|
||||
aria-label={localize('com_ui_unarchive')}
|
||||
title={localize('com_ui_unarchive_conversation')}
|
||||
aria-label={localize('com_ui_unarchive_conversation')}
|
||||
disabled={unarchiveMutation.isLoading}
|
||||
>
|
||||
{unarchiveMutation.isLoading ? (
|
||||
|
|
@ -243,7 +271,7 @@ export default function ArchivedChatsTable({
|
|||
}
|
||||
/>
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_delete')}
|
||||
description={localize('com_ui_delete_conversation_tooltip')}
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -252,8 +280,8 @@ export default function ArchivedChatsTable({
|
|||
setDeleteConversation(row.original);
|
||||
setIsDeleteOpen(true);
|
||||
}}
|
||||
title={localize('com_ui_delete')}
|
||||
aria-label={localize('com_ui_delete')}
|
||||
title={localize('com_ui_delete_conversation_tooltip')}
|
||||
aria-label={localize('com_ui_delete_conversation_tooltip')}
|
||||
>
|
||||
<TrashIcon className="size-4" />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -883,6 +883,7 @@
|
|||
"com_ui_delete_confirm_prompt_version_var": "This will delete the selected version for \"{{0}}.\" If no other versions exist, the prompt will be deleted.",
|
||||
"com_ui_delete_confirm_strong": "This will delete <strong>{{title}}</strong>",
|
||||
"com_ui_delete_conversation": "Delete chat?",
|
||||
"com_ui_delete_conversation_tooltip": "Delete conversation",
|
||||
"com_ui_delete_memory": "Delete Memory",
|
||||
"com_ui_delete_not_allowed": "Delete operation is not allowed",
|
||||
"com_ui_delete_preset": "Delete Preset?",
|
||||
|
|
@ -1176,11 +1177,11 @@
|
|||
"com_ui_off": "Off",
|
||||
"com_ui_offline": "Offline",
|
||||
"com_ui_on": "On",
|
||||
"com_ui_open_archived_chat_new_tab_title": "{{title}} (opens in new tab)",
|
||||
"com_ui_open_source_chat_new_tab": "Open Source Chat in New Tab",
|
||||
"com_ui_open_source_chat_new_tab_title": "Open Source Chat in New Tab - {{title}}",
|
||||
"com_ui_open_var": "Open {{0}}",
|
||||
"com_ui_openai": "OpenAI",
|
||||
"com_ui_opens_new_tab": "(opens in new tab)",
|
||||
"com_ui_optional": "(optional)",
|
||||
"com_ui_page": "Page",
|
||||
"com_ui_people": "people",
|
||||
|
|
@ -1376,6 +1377,7 @@
|
|||
"com_ui_ui_resource_not_found": "UI Resource not found (index: {{0}})",
|
||||
"com_ui_ui_resources": "UI Resources",
|
||||
"com_ui_unarchive": "Unarchive",
|
||||
"com_ui_unarchive_conversation": "Unarchive conversation",
|
||||
"com_ui_unarchive_error": "Failed to unarchive conversation",
|
||||
"com_ui_unavailable": "Unavailable",
|
||||
"com_ui_unknown": "Unknown",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue