mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
↕️ feat: Improve Sorting Accessibility in Archived Chats and Shared Links Modals (#10973)
* fix: cast translation keys for ESLint * fix: sort by header withi keyboard nav now retains focus on press * fix: focus retained on key press for sorts in archived chat table * fix: cast translation keys for ESLint
This commit is contained in:
parent
f11817a30e
commit
6ae839c14d
2 changed files with 71 additions and 76 deletions
|
|
@ -12,6 +12,7 @@ import {
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { SharedLinkItem, SharedLinksListParams } from 'librechat-data-provider';
|
import type { SharedLinkItem, SharedLinksListParams } from 'librechat-data-provider';
|
||||||
|
import type { TranslationKeys } from '~/hooks';
|
||||||
import {
|
import {
|
||||||
OGDialog,
|
OGDialog,
|
||||||
useToastContext,
|
useToastContext,
|
||||||
|
|
@ -62,14 +63,6 @@ export default function SharedLinks() {
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSort = useCallback((sortField: string, sortOrder: 'asc' | 'desc') => {
|
|
||||||
setQueryParams((prev) => ({
|
|
||||||
...prev,
|
|
||||||
sortBy: sortField as 'title' | 'createdAt',
|
|
||||||
sortDirection: sortOrder,
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleFilterChange = useCallback((value: string) => {
|
const handleFilterChange = useCallback((value: string) => {
|
||||||
const encodedValue = encodeURIComponent(value.trim());
|
const encodedValue = encodeURIComponent(value.trim());
|
||||||
setQueryParams((prev) => ({
|
setQueryParams((prev) => ({
|
||||||
|
|
@ -120,7 +113,7 @@ export default function SharedLinks() {
|
||||||
|
|
||||||
if (validRows.length === 0) {
|
if (validRows.length === 0) {
|
||||||
showToast({
|
showToast({
|
||||||
message: localize('com_ui_no_valid_items'),
|
message: localize('com_ui_no_valid_items' as TranslationKeys),
|
||||||
severity: NotificationSeverity.WARNING,
|
severity: NotificationSeverity.WARNING,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
@ -134,15 +127,15 @@ export default function SharedLinks() {
|
||||||
showToast({
|
showToast({
|
||||||
message: localize(
|
message: localize(
|
||||||
validRows.length === 1
|
validRows.length === 1
|
||||||
? 'com_ui_shared_link_delete_success'
|
? ('com_ui_shared_link_delete_success' as TranslationKeys)
|
||||||
: 'com_ui_shared_link_bulk_delete_success',
|
: ('com_ui_shared_link_bulk_delete_success' as TranslationKeys),
|
||||||
),
|
),
|
||||||
severity: NotificationSeverity.SUCCESS,
|
severity: NotificationSeverity.SUCCESS,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete shared links:', error);
|
console.error('Failed to delete shared links:', error);
|
||||||
showToast({
|
showToast({
|
||||||
message: localize('com_ui_bulk_delete_error'),
|
message: localize('com_ui_bulk_delete_error' as TranslationKeys),
|
||||||
severity: NotificationSeverity.ERROR,
|
severity: NotificationSeverity.ERROR,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -168,26 +161,28 @@ export default function SharedLinks() {
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
accessorKey: 'title',
|
accessorKey: 'title',
|
||||||
header: () => {
|
header: ({ column }) => {
|
||||||
const isSorted = queryParams.sortBy === 'title';
|
const sortState = column.getIsSorted();
|
||||||
const sortDirection = queryParams.sortDirection;
|
let SortIcon = ArrowUpDown;
|
||||||
|
let ariaSort: 'ascending' | 'descending' | 'none' = 'none';
|
||||||
|
if (sortState === 'desc') {
|
||||||
|
SortIcon = ArrowDown;
|
||||||
|
ariaSort = 'descending';
|
||||||
|
} else if (sortState === 'asc') {
|
||||||
|
SortIcon = ArrowUp;
|
||||||
|
ariaSort = 'ascending';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
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"
|
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||||
onClick={() =>
|
aria-sort={ariaSort}
|
||||||
handleSort('title', isSorted && sortDirection === 'asc' ? 'desc' : 'asc')
|
|
||||||
}
|
|
||||||
aria-label={localize('com_ui_name_sort')}
|
aria-label={localize('com_ui_name_sort')}
|
||||||
|
aria-current={sortState ? 'true' : 'false'}
|
||||||
>
|
>
|
||||||
{localize('com_ui_name')}
|
{localize('com_ui_name')}
|
||||||
{isSorted && sortDirection === 'asc' && (
|
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
<ArrowUp className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
|
||||||
)}
|
|
||||||
{isSorted && sortDirection === 'desc' && (
|
|
||||||
<ArrowDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
|
||||||
)}
|
|
||||||
{!isSorted && <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />}
|
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -218,26 +213,28 @@ export default function SharedLinks() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'createdAt',
|
accessorKey: 'createdAt',
|
||||||
header: () => {
|
header: ({ column }) => {
|
||||||
const isSorted = queryParams.sortBy === 'createdAt';
|
const sortState = column.getIsSorted();
|
||||||
const sortDirection = queryParams.sortDirection;
|
let SortIcon = ArrowUpDown;
|
||||||
|
let ariaSort: 'ascending' | 'descending' | 'none' = 'none';
|
||||||
|
if (sortState === 'desc') {
|
||||||
|
SortIcon = ArrowDown;
|
||||||
|
ariaSort = 'descending';
|
||||||
|
} else if (sortState === 'asc') {
|
||||||
|
SortIcon = ArrowUp;
|
||||||
|
ariaSort = 'ascending';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
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"
|
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||||
onClick={() =>
|
aria-sort={ariaSort}
|
||||||
handleSort('createdAt', isSorted && sortDirection === 'asc' ? 'desc' : 'asc')
|
aria-label={localize('com_ui_creation_date_sort' as TranslationKeys)}
|
||||||
}
|
aria-current={sortState ? 'true' : 'false'}
|
||||||
aria-label={localize('com_ui_creation_date_sort')}
|
|
||||||
>
|
>
|
||||||
{localize('com_ui_date')}
|
{localize('com_ui_date')}
|
||||||
{isSorted && sortDirection === 'asc' && (
|
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
<ArrowUp className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
|
||||||
)}
|
|
||||||
{isSorted && sortDirection === 'desc' && (
|
|
||||||
<ArrowDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
|
||||||
)}
|
|
||||||
{!isSorted && <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />}
|
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -300,7 +297,7 @@ export default function SharedLinks() {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[isSmallScreen, localize, queryParams, handleSort],
|
[isSmallScreen, localize],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import {
|
||||||
OGDialogContent,
|
OGDialogContent,
|
||||||
} from '@librechat/client';
|
} from '@librechat/client';
|
||||||
import type { ConversationListParams, TConversation } from 'librechat-data-provider';
|
import type { ConversationListParams, TConversation } from 'librechat-data-provider';
|
||||||
|
import type { TranslationKeys } from '~/hooks';
|
||||||
import {
|
import {
|
||||||
useConversationsInfiniteQuery,
|
useConversationsInfiniteQuery,
|
||||||
useDeleteConversationMutation,
|
useDeleteConversationMutation,
|
||||||
|
|
@ -56,14 +57,6 @@ export default function ArchivedChatsTable({
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSort = useCallback((sortField: string, sortOrder: 'asc' | 'desc') => {
|
|
||||||
setQueryParams((prev) => ({
|
|
||||||
...prev,
|
|
||||||
sortBy: sortField as 'title' | 'createdAt',
|
|
||||||
sortDirection: sortOrder,
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleFilterChange = useCallback((value: string) => {
|
const handleFilterChange = useCallback((value: string) => {
|
||||||
const encodedValue = encodeURIComponent(value.trim());
|
const encodedValue = encodeURIComponent(value.trim());
|
||||||
setQueryParams((prev) => ({
|
setQueryParams((prev) => ({
|
||||||
|
|
@ -133,25 +126,28 @@ export default function ArchivedChatsTable({
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
accessorKey: 'title',
|
accessorKey: 'title',
|
||||||
header: () => {
|
header: ({ column }) => {
|
||||||
const isSorted = queryParams.sortBy === 'title';
|
const sortState = column.getIsSorted();
|
||||||
const sortDirection = queryParams.sortDirection;
|
let SortIcon = ArrowUpDown;
|
||||||
|
let ariaSort: 'ascending' | 'descending' | 'none' = 'none';
|
||||||
|
if (sortState === 'desc') {
|
||||||
|
SortIcon = ArrowDown;
|
||||||
|
ariaSort = 'descending';
|
||||||
|
} else if (sortState === 'asc') {
|
||||||
|
SortIcon = ArrowUp;
|
||||||
|
ariaSort = 'ascending';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
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"
|
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||||
onClick={() =>
|
aria-sort={ariaSort}
|
||||||
handleSort('title', isSorted && sortDirection === 'asc' ? 'desc' : 'asc')
|
aria-label={localize('com_nav_archive_name_sort' as TranslationKeys)}
|
||||||
}
|
aria-current={sortState ? 'true' : 'false'}
|
||||||
>
|
>
|
||||||
{localize('com_nav_archive_name')}
|
{localize('com_nav_archive_name')}
|
||||||
{isSorted && sortDirection === 'asc' && (
|
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
<ArrowUp className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
|
||||||
)}
|
|
||||||
{isSorted && sortDirection === 'desc' && (
|
|
||||||
<ArrowDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
|
||||||
)}
|
|
||||||
{!isSorted && <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />}
|
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -180,26 +176,28 @@ export default function ArchivedChatsTable({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'createdAt',
|
accessorKey: 'createdAt',
|
||||||
header: () => {
|
header: ({ column }) => {
|
||||||
const isSorted = queryParams.sortBy === 'createdAt';
|
const sortState = column.getIsSorted();
|
||||||
const sortDirection = queryParams.sortDirection;
|
let SortIcon = ArrowUpDown;
|
||||||
|
let ariaSort: 'ascending' | 'descending' | 'none' = 'none';
|
||||||
|
if (sortState === 'desc') {
|
||||||
|
SortIcon = ArrowDown;
|
||||||
|
ariaSort = 'descending';
|
||||||
|
} else if (sortState === 'asc') {
|
||||||
|
SortIcon = ArrowUp;
|
||||||
|
ariaSort = 'ascending';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
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"
|
className="px-2 py-0 text-xs hover:bg-surface-hover sm:px-2 sm:py-2 sm:text-sm"
|
||||||
onClick={() =>
|
aria-sort={ariaSort}
|
||||||
handleSort('createdAt', isSorted && sortDirection === 'asc' ? 'desc' : 'asc')
|
aria-label={localize('com_nav_archive_created_at_sort' as TranslationKeys)}
|
||||||
}
|
aria-current={sortState ? 'true' : 'false'}
|
||||||
aria-label={localize('com_nav_archive_created_at_sort')}
|
|
||||||
>
|
>
|
||||||
{localize('com_nav_archive_created_at')}
|
{localize('com_nav_archive_created_at')}
|
||||||
{isSorted && sortDirection === 'asc' && (
|
<SortIcon className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
||||||
<ArrowUp className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
|
||||||
)}
|
|
||||||
{isSorted && sortDirection === 'desc' && (
|
|
||||||
<ArrowDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />
|
|
||||||
)}
|
|
||||||
{!isSorted && <ArrowUpDown className="ml-2 h-3 w-4 sm:h-4 sm:w-4" />}
|
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -270,7 +268,7 @@ export default function ArchivedChatsTable({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[handleSort, isSmallScreen, localize, queryParams, unarchiveMutation],
|
[isSmallScreen, localize, unarchiveMutation],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue