mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-29 22:58:51 +01:00
Fixes all Nav Menu related errors and bugs (#331)
* chore(client): update lucide-react package to version 0.220.0 style(client): change color of MessageHeader component text to gray-500 style(client): change color of nav-close-button to gray-400 and nav-open-button to gray-500 feat(client): add Panel component to replace svg icons in Nav component * fix: forwardRef errors in Nav Menu * refactor(SearchBar.jsx): change clearSearch prop destructuring to props destructuring refactor(SearchBar.jsx): add ref prop to SearchBar component refactor(getIcon.jsx): remove unused imports refactor(getIcon.jsx): add nullish coalescing operator to user.name and user.avatar properties * fix (NavLinks): modals no longer close on nav menu close * style(ExportModel.jsx): remove unnecessary z-index property from a div element * style(ExportModel.jsx): remove trailing whitespace in input element * refactor(Message.jsx): remove unused cancelled variable fix(Message.jsx): fix error message length exceeding 512 characters refactor(MenuItem.jsx): remove unused MenuItem component
This commit is contained in:
parent
ee2b3e4fb2
commit
ec561fcd7f
16 changed files with 230 additions and 165 deletions
|
|
@ -1,11 +1,10 @@
|
|||
import { useEffect } from 'react';
|
||||
import store from '~/store';
|
||||
import TrashIcon from '../svg/TrashIcon';
|
||||
import { Dialog, DialogTrigger } from '../ui/Dialog.tsx';
|
||||
import { Dialog } from '../ui/Dialog.tsx';
|
||||
import DialogTemplate from '../ui/DialogTemplate';
|
||||
import { useClearConversationsMutation } from '~/data-provider';
|
||||
|
||||
export default function ClearConvos() {
|
||||
const ClearConvos = ({ open, onOpenChange}) => {
|
||||
const { newConversation } = store.useConversation();
|
||||
const { refreshConversations } = store.useConversations();
|
||||
const clearConvosMutation = useClearConversationsMutation();
|
||||
|
|
@ -23,13 +22,7 @@ export default function ClearConvos() {
|
|||
}, [clearConvosMutation.isSuccess]);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700">
|
||||
<TrashIcon />
|
||||
Clear conversations
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogTemplate
|
||||
title="Clear conversations"
|
||||
description="Are you sure you want to clear all conversations? This is irreversible."
|
||||
|
|
@ -41,4 +34,6 @@ export default function ClearConvos() {
|
|||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ClearConvos;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useState, useContext } from 'react';
|
||||
import { forwardRef, useContext } from 'react';
|
||||
import DarkModeIcon from '../svg/DarkModeIcon';
|
||||
import LightModeIcon from '../svg/LightModeIcon';
|
||||
import { ThemeContext } from '~/hooks/ThemeContext';
|
||||
|
||||
export default function DarkMode() {
|
||||
const DarkMode = forwardRef(() => {
|
||||
const { theme, setTheme } = useContext(ThemeContext);
|
||||
|
||||
const clickHandler = () => setTheme(theme === 'dark' ? 'light' : 'dark');
|
||||
|
|
@ -18,4 +18,6 @@ export default function DarkMode() {
|
|||
{mode}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default DarkMode;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue, useRecoilCallback } from 'recoil';
|
||||
import filenamify from 'filenamify';
|
||||
import exportFromJSON from 'export-from-json';
|
||||
import download from 'downloadjs';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate.jsx';
|
||||
import { Dialog, DialogClose, DialogButton } from '~/components/ui/Dialog.tsx';
|
||||
import { Dialog, DialogButton } from '~/components/ui/Dialog.tsx';
|
||||
import { Input } from '~/components/ui/Input.tsx';
|
||||
import { Label } from '~/components/ui/Label.tsx';
|
||||
import { Checkbox } from '~/components/ui/Checkbox.tsx';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useState, forwardRef } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Download } from 'lucide-react';
|
||||
import { cn } from '~/utils/';
|
||||
|
|
@ -7,7 +7,7 @@ import ExportModel from './ExportModel';
|
|||
|
||||
import store from '~/store';
|
||||
|
||||
export default function ExportConversation() {
|
||||
const ExportConversation = forwardRef(() => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const conversation = useRecoilValue(store.conversation) || {};
|
||||
|
|
@ -37,4 +37,6 @@ export default function ExportConversation() {
|
|||
<ExportModel open={open} onOpenChange={setOpen} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default ExportConversation;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import LogOutIcon from '../svg/LogOutIcon';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
|
||||
export default function Logout() {
|
||||
const Logout = forwardRef(() => {
|
||||
const { user, logout } = useAuthContext();
|
||||
|
||||
const handleLogout = () => {
|
||||
|
|
@ -20,4 +20,6 @@ export default function Logout() {
|
|||
<small>Log out</small>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Logout;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
import React from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
export default function NavLink({ svg, text, clickHandler }) {
|
||||
const props = {
|
||||
className:
|
||||
'flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10'
|
||||
};
|
||||
const NavLink = forwardRef((props, ref) => {
|
||||
const { svg, text, clickHandler, className = '' } = props;
|
||||
const defaultProps = {};
|
||||
|
||||
defaultProps.className = cn(
|
||||
'flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10',
|
||||
className
|
||||
);
|
||||
|
||||
if (clickHandler) {
|
||||
props.onClick = clickHandler;
|
||||
console.log('clickHandler: ', clickHandler);
|
||||
defaultProps.onClick = clickHandler;
|
||||
}
|
||||
|
||||
return (
|
||||
<a {...props}>
|
||||
<a {...defaultProps} ref={ref}>
|
||||
{svg()}
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default NavLink;
|
||||
|
|
|
|||
|
|
@ -1,70 +1,111 @@
|
|||
import { Menu, Transition } from '@headlessui/react';
|
||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import SearchBar from './SearchBar';
|
||||
import TrashIcon from '../svg/TrashIcon';
|
||||
import { Download } from 'lucide-react';
|
||||
import NavLink from './NavLink';
|
||||
import ExportModel from './ExportConversation/ExportModel';
|
||||
import ClearConvos from './ClearConvos';
|
||||
import DarkMode from './DarkMode';
|
||||
import Logout from './Logout';
|
||||
import ExportConversation from './ExportConversation';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import { cn } from '~/utils/';
|
||||
import DotsIcon from '../svg/DotsIcon';
|
||||
|
||||
import store from '~/store';
|
||||
|
||||
export default function NavLinks({ clearSearch, isSearchEnabled }) {
|
||||
const { user, logout } = useAuthContext();
|
||||
const [showExports, setShowExports] = useState(false);
|
||||
const [showClearConvos, setShowClearConvos] = useState(false);
|
||||
const { user } = useAuthContext();
|
||||
|
||||
const conversation = useRecoilValue(store.conversation) || {};
|
||||
|
||||
const exportable =
|
||||
conversation?.conversationId &&
|
||||
conversation?.conversationId !== 'new' &&
|
||||
conversation?.conversationId !== 'search';
|
||||
|
||||
const clickHandler = () => {
|
||||
if (exportable) setShowExports(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu as="div" className="group relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button
|
||||
className={cn(
|
||||
'group-ui-open:bg-gray-800 flex w-full items-center gap-2.5 rounded-md px-3 py-3 text-sm transition-colors duration-200 hover:bg-gray-800',
|
||||
open ? 'bg-gray-800' : ''
|
||||
)}
|
||||
>
|
||||
<div className="-ml-0.5 h-5 w-5 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
<img
|
||||
className="rounded-sm"
|
||||
src={
|
||||
user?.avatar || `https://avatars.dicebear.com/api/initials/${user?.name}.svg`
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
<>
|
||||
<Menu as="div" className="group relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button
|
||||
className={cn(
|
||||
'group-ui-open:bg-gray-800 flex w-full items-center gap-2.5 rounded-md px-3 py-3 text-sm transition-colors duration-200 hover:bg-gray-800',
|
||||
open ? 'bg-gray-800' : ''
|
||||
)}
|
||||
>
|
||||
<div className="-ml-0.5 h-5 w-5 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
<img
|
||||
className="rounded-sm"
|
||||
src={
|
||||
user?.avatar || `https://avatars.dicebear.com/api/initials/${user?.name}.svg`
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-white">
|
||||
{user?.name || 'USER'}
|
||||
</div>
|
||||
<DotsIcon />
|
||||
</Menu.Button>
|
||||
<div className="grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-white">
|
||||
{user?.name || 'USER'}
|
||||
</div>
|
||||
<DotsIcon />
|
||||
</Menu.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute bottom-full left-0 z-20 mb-2 w-full translate-y-0 overflow-hidden rounded-md bg-[#050509] py-1.5 opacity-100 outline-none">
|
||||
<Menu.Item>
|
||||
{({}) => <>{!!isSearchEnabled && <SearchBar clearSearch={clearSearch} />}</>}
|
||||
</Menu.Item>
|
||||
<Menu.Item>{({}) => <ExportConversation />}</Menu.Item>
|
||||
|
||||
<div className="my-1.5 h-px bg-white/20" role="none"></div>
|
||||
<Menu.Item>{({}) => <DarkMode />}</Menu.Item>
|
||||
<Menu.Item>{({}) => <ClearConvos />}</Menu.Item>
|
||||
|
||||
<div className="my-1.5 h-px bg-white/20" role="none"></div>
|
||||
<Menu.Item>
|
||||
<Logout />
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute bottom-full left-0 z-20 mb-2 w-full translate-y-0 overflow-hidden rounded-md bg-[#050509] py-1.5 opacity-100 outline-none">
|
||||
<Menu.Item>
|
||||
{!!isSearchEnabled && <SearchBar clearSearch={clearSearch} />}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<NavLink
|
||||
className={cn(
|
||||
'flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700',
|
||||
exportable ? 'cursor-pointer text-white' : 'cursor-not-allowed text-gray-400'
|
||||
)}
|
||||
svg={() => <Download size={16} />}
|
||||
text="Export conversation"
|
||||
clickHandler={clickHandler}
|
||||
/>
|
||||
</Menu.Item>
|
||||
<div className="my-1.5 h-px bg-white/20" role="none" />
|
||||
<Menu.Item>
|
||||
<DarkMode />
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<NavLink
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
|
||||
svg={() => <TrashIcon />}
|
||||
text="Clear conversations"
|
||||
clickHandler={() => setShowClearConvos(true)}
|
||||
/>
|
||||
</Menu.Item>
|
||||
<div className="my-1.5 h-px bg-white/20" role="none" />
|
||||
<Menu.Item>
|
||||
<Logout />
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
{showExports && <ExportModel open={showExports} onOpenChange={setShowExports} />}
|
||||
{showClearConvos && <ClearConvos open={showClearConvos} onOpenChange={setShowClearConvos} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { forwardRef } from 'react';
|
||||
import { Search } from 'lucide-react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import store from '~/store';
|
||||
|
||||
export default function SearchBar({ clearSearch }) {
|
||||
const SearchBar = forwardRef((props, ref) => {
|
||||
const { clearSearch } = props;
|
||||
const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery);
|
||||
|
||||
const handleKeyUp = (e) => {
|
||||
|
|
@ -19,7 +21,10 @@ export default function SearchBar({ clearSearch }) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700">
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
|
||||
>
|
||||
{<Search className="h-4 w-4" />}
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -34,4 +39,6 @@ export default function SearchBar({ clearSearch }) {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default SearchBar;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState, useEffect, useRef, useContext } from 'react';
|
||||
import NewChat from './NewChat';
|
||||
import Panel from '../svg/Panel';
|
||||
import Spinner from '../svg/Spinner';
|
||||
import Pages from '../Conversations/Pages';
|
||||
import Conversations from '../Conversations';
|
||||
|
|
@ -198,62 +199,21 @@ export default function Nav({ navVisible, setNavVisible }) {
|
|||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={cn('nav-close-button -ml-0.5 -mt-2.5 inline-flex h-10 w-10 items-center justify-center rounded-md focus:outline-none focus:ring-white md:-ml-1 md:-mt-2.5', theme === 'dark' ? 'text-gray-500 hover:text-gray-400' : 'text-gray-900 hover:text-gray-600')}
|
||||
className={cn('nav-close-button -ml-0.5 -mt-2.5 inline-flex h-10 w-10 items-center justify-center rounded-md focus:outline-none focus:ring-white md:-ml-1 md:-mt-2.5', theme === 'dark' ? 'text-gray-500 hover:text-gray-400' : 'text-gray-400 hover:text-gray-500')}
|
||||
onClick={toggleNavVisible}
|
||||
>
|
||||
<span className="sr-only">Close sidebar</span>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="block h-6 w-6 md:hidden"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line x1="3" y1="6" x2="15" y2="18" />
|
||||
<line x1="3" y1="18" x2="15" y2="6" />
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="hidden h-[26px] w-[26px] md:block"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75"
|
||||
/>
|
||||
</svg>
|
||||
<Panel/>
|
||||
</button>
|
||||
</div>
|
||||
{!navVisible && (
|
||||
<button
|
||||
type="button"
|
||||
className="nav-open-button fixed left-2 top-0.5 z-10 inline-flex h-10 w-10 items-center justify-center rounded-md text-gray-900 hover:text-gray-600 focus:outline-none focus:ring-white dark:text-gray-500 dark:hover:text-gray-400"
|
||||
className="nav-open-button fixed left-2 top-0.5 z-10 inline-flex h-10 w-10 items-center justify-center rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-white dark:text-gray-500 dark:hover:text-gray-400"
|
||||
onClick={toggleNavVisible}
|
||||
>
|
||||
<span className="sr-only">Open sidebar</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9"
|
||||
/>
|
||||
</svg>
|
||||
<Panel open={true}/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue