* fix: remove side panel elements from screen reader when hidden There's both left & right side panels; elements of both of them are hidden when dismissed. However, currently they are being hidden by using classes to hide their UI (such as making the sidebar zero width). That works for visually dismissing these elements, but they can still be viewed by a screen reader (using the tab key to jump between interactable elements). That can be a rather confusing experience for anyone visually impaired (such as duplicate buttons, or buttons that do nothing). -------- I've changed it so hidden elements are fully removed from the render. This prevents them from being interactable via keyboard. I leveraged Motion to duplicate the animations as they happened before. I subtly cleaned up the animations while I was at it. * Implemented reasonable suggestions from Copilot review
88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { useMediaQuery } from '@librechat/client';
|
|
import { useOutletContext } from 'react-router-dom';
|
|
import { getConfigDefaults, PermissionTypes, Permissions } from 'librechat-data-provider';
|
|
import type { ContextType } from '~/common';
|
|
import ModelSelector from './Menus/Endpoints/ModelSelector';
|
|
import { PresetsMenu, HeaderNewChat, OpenSidebar } from './Menus';
|
|
import { useGetStartupConfig } from '~/data-provider';
|
|
import ExportAndShareMenu from './ExportAndShareMenu';
|
|
import BookmarkMenu from './Menus/BookmarkMenu';
|
|
import { TemporaryChat } from './TemporaryChat';
|
|
import AddMultiConvo from './AddMultiConvo';
|
|
import { useHasAccess } from '~/hooks';
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
|
|
const defaultInterface = getConfigDefaults().interface;
|
|
|
|
export default function Header() {
|
|
const { data: startupConfig } = useGetStartupConfig();
|
|
const { navVisible, setNavVisible } = useOutletContext<ContextType>();
|
|
|
|
const interfaceConfig = useMemo(
|
|
() => startupConfig?.interface ?? defaultInterface,
|
|
[startupConfig],
|
|
);
|
|
|
|
const hasAccessToBookmarks = useHasAccess({
|
|
permissionType: PermissionTypes.BOOKMARKS,
|
|
permission: Permissions.USE,
|
|
});
|
|
|
|
const hasAccessToMultiConvo = useHasAccess({
|
|
permissionType: PermissionTypes.MULTI_CONVO,
|
|
permission: Permissions.USE,
|
|
});
|
|
|
|
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
|
|
|
return (
|
|
<div className="sticky top-0 z-10 flex h-14 w-full items-center justify-between bg-white p-2 font-semibold text-text-primary dark:bg-gray-800">
|
|
<div className="hide-scrollbar flex w-full items-center justify-between gap-2 overflow-x-auto">
|
|
<div className="mx-1 flex items-center">
|
|
<AnimatePresence initial={false}>
|
|
{!navVisible && (
|
|
<motion.div
|
|
className={`flex items-center gap-2`}
|
|
initial={{ width: 0, opacity: 0 }}
|
|
animate={{ width: 'auto', opacity: 1 }}
|
|
exit={{ width: 0, opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
key="header-buttons"
|
|
>
|
|
<OpenSidebar setNavVisible={setNavVisible} className="max-md:hidden" />
|
|
<HeaderNewChat />
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
<div className={navVisible ? 'flex items-center gap-2' : 'ml-2 flex items-center gap-2'}>
|
|
<ModelSelector startupConfig={startupConfig} />
|
|
{interfaceConfig.presets === true && interfaceConfig.modelSelect && <PresetsMenu />}
|
|
{hasAccessToBookmarks === true && <BookmarkMenu />}
|
|
{hasAccessToMultiConvo === true && <AddMultiConvo />}
|
|
{isSmallScreen && (
|
|
<>
|
|
<ExportAndShareMenu
|
|
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
|
|
/>
|
|
<TemporaryChat />
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{!isSmallScreen && (
|
|
<div className="flex items-center gap-2">
|
|
<ExportAndShareMenu
|
|
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
|
|
/>
|
|
<TemporaryChat />
|
|
</div>
|
|
)}
|
|
</div>
|
|
{/* Empty div for spacing */}
|
|
<div />
|
|
</div>
|
|
);
|
|
}
|