LibreChat/client/src/components/Prompts/Groups/List.tsx
Daniel Lew 1143f73f59
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Has been cancelled
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Has been cancelled
🔇 fix: Hide Button Icons from Screen Readers (#10776)
If you've got a screen reader that is reading out the whole page,
each icon button (i.e., `<button><SVG></button>`) will have both
the button's aria-label read out as well as the title from the
SVG (which is usually just "image").

Since we are pretty good about setting aria-labels, we should instead
use `aria-hidden="true"` on these images, since they are not useful
to be read out.

I don't consider this a comprehensive review of all icons in the app,
but I knocked out all the low hanging fruit in this commit.
2025-12-11 16:35:17 -05:00

82 lines
3.1 KiB
TypeScript

import { Plus } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { Button, Skeleton } from '@librechat/client';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
import type { TPromptGroup, TStartupConfig } from 'librechat-data-provider';
import DashGroupItem from '~/components/Prompts/Groups/DashGroupItem';
import ChatGroupItem from '~/components/Prompts/Groups/ChatGroupItem';
import { useGetStartupConfig } from '~/data-provider';
import { useLocalize, useHasAccess } from '~/hooks';
export default function List({
groups = [],
isChatRoute,
isLoading,
}: {
groups?: TPromptGroup[];
isChatRoute: boolean;
isLoading: boolean;
}) {
const navigate = useNavigate();
const localize = useLocalize();
const { data: startupConfig = {} as Partial<TStartupConfig> } = useGetStartupConfig();
const { instanceProjectId } = startupConfig;
const hasCreateAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.CREATE,
});
return (
<div className="flex h-full flex-col">
{hasCreateAccess && (
<div className="flex w-full justify-end">
<Button
variant="outline"
className={`w-full bg-transparent ${isChatRoute ? '' : 'mx-2'}`}
onClick={() => navigate('/d/prompts/new')}
aria-label={localize('com_ui_create_prompt')}
>
<Plus className="size-4" aria-hidden="true" />
{localize('com_ui_create_prompt')}
</Button>
</div>
)}
<div className="flex-grow overflow-y-auto" aria-label={localize('com_ui_prompt_groups')}>
<div className="overflow-y-auto overflow-x-hidden">
{isLoading && isChatRoute && (
<Skeleton className="my-2 flex h-[84px] w-full rounded-2xl border-0 px-3 pb-4 pt-3" />
)}
{isLoading &&
!isChatRoute &&
Array.from({ length: 10 }).map((_, index: number) => (
<Skeleton key={index} className="w-100 mx-2 my-2 flex h-14 rounded-lg border-0 p-4" />
))}
{!isLoading && groups.length === 0 && isChatRoute && (
<div className="my-2 flex h-[84px] w-full items-center justify-center rounded-2xl border border-border-light bg-transparent px-3 pb-4 pt-3 text-text-primary">
{localize('com_ui_nothing_found')}
</div>
)}
{!isLoading && groups.length === 0 && !isChatRoute && (
<div className="my-12 flex w-full items-center justify-center text-lg font-semibold text-text-primary">
{localize('com_ui_nothing_found')}
</div>
)}
{groups.map((group) => {
if (isChatRoute) {
return (
<ChatGroupItem
key={group._id}
group={group}
instanceProjectId={instanceProjectId}
/>
);
}
return (
<DashGroupItem key={group._id} group={group} instanceProjectId={instanceProjectId} />
);
})}
</div>
</div>
</div>
);
}