LibreChat/client/src/components/Prompts/Groups/GroupSidePanel.tsx
Dustin Healy 6fc6471010
☰ fix: Side Panel Accessibility Improvements (#10830)
* fix: pad cards so focus outline doesn't clip in prompts list

* feat: pad snippet top for space between text and button

* fix: prompt menu focus outline clipping with overflow-visible

* fix: clipping in AgentBuilder for advanced and admin buttons

* fix: clipping in memory panel for admin settings

* fix: better contrast thresholds on focus outlines for admin settings and advanced buttons in agent builder

* fix: better contrast thresholds on focus outlines for admin settings  button in memory panel

* fix: clipping on focus outline for manage files button in files panel

* fix: focus outline clipping table cells for files panel table

* fix: clipping on new bookmark button in bookmarks panel

* fix: clipping on Admin Settings button in MCP Settings panel

* fix: better contrast threshold outline and aria-label for Admin Settings button in MCP Settings panel

* fix: misaligned globe because of new unnested menu button positioning

* fix: localize global group aria-label

* fix: screen reader not reading out proper prompt name for dropdown menu button
2025-12-11 16:39:31 -05:00

82 lines
2.7 KiB
TypeScript

import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { Button, Sidebar, TooltipAnchor } from '@librechat/client';
import ManagePrompts from '~/components/Prompts/ManagePrompts';
import { usePromptGroupsContext } from '~/Providers';
import List from '~/components/Prompts/Groups/List';
import PanelNavigation from './PanelNavigation';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
export default function GroupSidePanel({
children,
className = '',
closePanelRef,
onClose,
}: {
children?: React.ReactNode;
className?: string;
closePanelRef?: React.RefObject<HTMLButtonElement>;
onClose?: () => void;
}) {
const location = useLocation();
const localize = useLocalize();
const isChatRoute = useMemo(() => location.pathname?.startsWith('/c/'), [location.pathname]);
const { promptGroups, groupsQuery, nextPage, prevPage, hasNextPage, hasPreviousPage } =
usePromptGroupsContext();
return (
<div
id="prompts-panel"
className={cn(
'flex h-full w-full flex-col md:mr-2 md:w-auto md:min-w-72 lg:w-1/4 xl:w-1/4',
className,
)}
>
{onClose && (
<div className="flex items-center justify-between px-2 py-[2px] md:py-2">
<TooltipAnchor
description={localize('com_nav_close_sidebar')}
render={
<Button
ref={closePanelRef}
size="icon"
variant="outline"
data-testid="close-prompts-panel-button"
aria-label={localize('com_nav_close_sidebar')}
aria-expanded={true}
className="rounded-full border-none bg-transparent p-2 hover:bg-surface-hover md:rounded-xl"
onClick={onClose}
>
<Sidebar />
</Button>
}
/>
</div>
)}
<div className="flex flex-1 flex-col gap-2 overflow-visible">
{children}
<div className={cn('relative flex h-full flex-col', isChatRoute ? '' : 'px-2 md:px-0')}>
<List
groups={promptGroups}
isChatRoute={isChatRoute}
isLoading={!!groupsQuery.isLoading}
/>
</div>
</div>
<div className={cn(isChatRoute ? '' : 'px-2 pb-3 pt-2 md:px-0')}>
<PanelNavigation
onPrevious={prevPage}
onNext={nextPage}
hasNextPage={hasNextPage}
hasPreviousPage={hasPreviousPage}
isLoading={groupsQuery.isFetching}
isChatRoute={isChatRoute}
>
{isChatRoute && <ManagePrompts className="select-none" />}
</PanelNavigation>
</div>
</div>
);
}