mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 01:10:14 +01:00
refactor(Nav): improve toggle animation, refactor to TS (#755)
* style(Nav): match transition effect of official site * fix(Pages): fix bug when searchResults pageSize is < prev PageSize causes currentPage to be impossible value * refactor/fix(Nav): fix width transition animation and refactor to TS
This commit is contained in:
parent
d6dbd56e33
commit
c7b586ba4c
4 changed files with 76 additions and 34 deletions
|
|
@ -1,10 +1,22 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { PagesProps } from 'librechat-data-provider';
|
||||||
|
|
||||||
export default function Pages({ pageNumber, pages, nextPage, previousPage }) {
|
export default function Pages({
|
||||||
const clickHandler = (func) => async (e) => {
|
pageNumber,
|
||||||
e.preventDefault();
|
pages,
|
||||||
await func();
|
nextPage,
|
||||||
};
|
previousPage,
|
||||||
|
setPageNumber,
|
||||||
|
}: PagesProps) {
|
||||||
|
const clickHandler =
|
||||||
|
(func: () => Promise<void>) => async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await func();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pageNumber > pages) {
|
||||||
|
setPageNumber(pages);
|
||||||
|
}
|
||||||
|
|
||||||
return pageNumber == 1 && pages == 1 ? null : (
|
return pageNumber == 1 && pages == 1 ? null : (
|
||||||
<div className="m-auto mb-2 mt-4 flex items-center justify-center gap-2">
|
<div className="m-auto mb-2 mt-4 flex items-center justify-center gap-2">
|
||||||
|
|
@ -1,30 +1,46 @@
|
||||||
|
import {
|
||||||
|
TConversation,
|
||||||
|
useGetConversationsQuery,
|
||||||
|
useSearchQuery,
|
||||||
|
TSearchResults,
|
||||||
|
} from 'librechat-data-provider';
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import NewChat from './NewChat';
|
import NewChat from './NewChat';
|
||||||
import NavLinks from './NavLinks';
|
import NavLinks from './NavLinks';
|
||||||
import { Panel, Spinner } from '~/components';
|
import { Panel, Spinner } from '~/components';
|
||||||
import { Conversations, Pages } from '../Conversations';
|
import { Conversations, Pages } from '../Conversations';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useAuthContext, useDebounce, useMediaQuery, useLocalize } from '~/hooks';
|
||||||
import { useGetConversationsQuery, useSearchQuery } from 'librechat-data-provider';
|
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
|
||||||
import { useAuthContext, useDebounce } from '~/hooks';
|
|
||||||
import { localize } from '~/localization/Translation';
|
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function Nav({ navVisible, setNavVisible }) {
|
export default function Nav({ navVisible, setNavVisible }) {
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
|
const [navWidth, setNavWidth] = useState('260px');
|
||||||
const { isAuthenticated } = useAuthContext();
|
const { isAuthenticated } = useAuthContext();
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const scrollPositionRef = useRef(null);
|
const scrollPositionRef = useRef<number | null>(null);
|
||||||
const lang = useRecoilValue(store.lang);
|
const localize = useLocalize();
|
||||||
|
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||||
|
|
||||||
const [conversations, setConversations] = useState([]);
|
useEffect(() => {
|
||||||
|
if (isSmallScreen) {
|
||||||
|
setNavWidth('320px');
|
||||||
|
} else {
|
||||||
|
setNavWidth('260px');
|
||||||
|
}
|
||||||
|
}, [isSmallScreen]);
|
||||||
|
|
||||||
|
const [conversations, setConversations] = useState<TConversation[]>([]);
|
||||||
// current page
|
// current page
|
||||||
const [pageNumber, setPageNumber] = useState(1);
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
// total pages
|
// total pages
|
||||||
const [pages, setPages] = useState(1);
|
const [pages, setPages] = useState(1);
|
||||||
|
|
||||||
// data provider
|
// data provider
|
||||||
const getConversationsQuery = useGetConversationsQuery(pageNumber, { enabled: isAuthenticated });
|
const getConversationsQuery = useGetConversationsQuery(pageNumber + '', {
|
||||||
|
enabled: isAuthenticated,
|
||||||
|
});
|
||||||
|
|
||||||
// search
|
// search
|
||||||
const searchQuery = useRecoilValue(store.searchQuery);
|
const searchQuery = useRecoilValue(store.searchQuery);
|
||||||
|
|
@ -42,22 +58,28 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
const [isFetching, setIsFetching] = useState(false);
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
|
|
||||||
const debouncedSearchTerm = useDebounce(searchQuery, 750);
|
const debouncedSearchTerm = useDebounce(searchQuery, 750);
|
||||||
const searchQueryFn = useSearchQuery(debouncedSearchTerm, pageNumber, {
|
const searchQueryFn = useSearchQuery(debouncedSearchTerm, pageNumber + '', {
|
||||||
enabled:
|
enabled: !!(
|
||||||
!!debouncedSearchTerm && debouncedSearchTerm.length > 0 && isSearchEnabled && isSearching,
|
!!debouncedSearchTerm &&
|
||||||
|
debouncedSearchTerm.length > 0 &&
|
||||||
|
isSearchEnabled &&
|
||||||
|
isSearching
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSearchSuccess = (data, expectedPage) => {
|
const onSearchSuccess = useCallback((data: TSearchResults, expectedPage?: number) => {
|
||||||
const res = data;
|
const res = data;
|
||||||
setConversations(res.conversations);
|
setConversations(res.conversations);
|
||||||
if (expectedPage) {
|
if (expectedPage) {
|
||||||
setPageNumber(expectedPage);
|
setPageNumber(expectedPage);
|
||||||
}
|
}
|
||||||
setPages(res.pages);
|
setPages(Number(res.pages));
|
||||||
setIsFetching(false);
|
setIsFetching(false);
|
||||||
searchPlaceholderConversation();
|
searchPlaceholderConversation();
|
||||||
setSearchResultMessages(res.messages);
|
setSearchResultMessages(res.messages);
|
||||||
};
|
/* disabled due recoil methods not recognized as state setters */
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []); // Empty dependency array
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//we use isInitialLoading here instead of isLoading because query is disabled by default
|
//we use isInitialLoading here instead of isLoading because query is disabled by default
|
||||||
|
|
@ -66,7 +88,7 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
} else if (searchQueryFn.data) {
|
} else if (searchQueryFn.data) {
|
||||||
onSearchSuccess(searchQueryFn.data);
|
onSearchSuccess(searchQueryFn.data);
|
||||||
}
|
}
|
||||||
}, [searchQueryFn.data, searchQueryFn.isInitialLoading]);
|
}, [searchQueryFn.data, searchQueryFn.isInitialLoading, onSearchSuccess]);
|
||||||
|
|
||||||
const clearSearch = () => {
|
const clearSearch = () => {
|
||||||
setPageNumber(1);
|
setPageNumber(1);
|
||||||
|
|
@ -99,12 +121,13 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let { conversations, pages } = getConversationsQuery.data;
|
let { conversations, pages } = getConversationsQuery.data;
|
||||||
|
pages = Number(pages);
|
||||||
if (pageNumber > pages) {
|
if (pageNumber > pages) {
|
||||||
setPageNumber(pages);
|
setPageNumber(pages);
|
||||||
} else {
|
} else {
|
||||||
if (!isSearching) {
|
if (!isSearching) {
|
||||||
conversations = conversations.sort(
|
conversations = conversations.sort(
|
||||||
(a, b) => new Date(b.createdAt) - new Date(a.createdAt),
|
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setConversations(conversations);
|
setConversations(conversations);
|
||||||
|
|
@ -121,7 +144,7 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
}, [pageNumber, conversationId, refreshConversationsHint]);
|
}, [pageNumber, conversationId, refreshConversationsHint]);
|
||||||
|
|
||||||
const toggleNavVisible = () => {
|
const toggleNavVisible = () => {
|
||||||
setNavVisible((prev) => !prev);
|
setNavVisible((prev: boolean) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const containerClasses =
|
const containerClasses =
|
||||||
|
|
@ -132,10 +155,11 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="nav active dark max-w-[320px] flex-shrink-0 overflow-x-hidden bg-gray-900 transition-all duration-200 ease-in-out md:max-w-[260px]"
|
className="nav active dark max-w-[320px] flex-shrink-0 overflow-x-hidden bg-gray-900 md:max-w-[260px]"
|
||||||
style={{
|
style={{
|
||||||
width: navVisible ? '100%' : '0%',
|
width: navVisible ? navWidth : '0px',
|
||||||
visibility: navVisible ? 'visible' : 'hidden',
|
visibility: navVisible ? 'visible' : 'hidden',
|
||||||
|
transition: 'width 0.2s, visibility 0.2s',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="h-full w-[320px] md:w-[260px]">
|
<div className="h-full w-[320px] md:w-[260px]">
|
||||||
|
|
@ -151,7 +175,7 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
)}
|
)}
|
||||||
onClick={toggleNavVisible}
|
onClick={toggleNavVisible}
|
||||||
>
|
>
|
||||||
<span className="sr-only">{localize(lang, 'com_nav_close_sidebar')}</span>
|
<span className="sr-only">{localize('com_nav_close_sidebar')}</span>
|
||||||
<Panel open={false} />
|
<Panel open={false} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -167,17 +191,14 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
{(getConversationsQuery.isLoading && pageNumber === 1) || isFetching ? (
|
{(getConversationsQuery.isLoading && pageNumber === 1) || isFetching ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
<Conversations
|
<Conversations conversations={conversations} moveToTop={moveToTop} />
|
||||||
conversations={conversations}
|
|
||||||
conversationId={conversationId}
|
|
||||||
moveToTop={moveToTop}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<Pages
|
<Pages
|
||||||
pageNumber={pageNumber}
|
pageNumber={pageNumber}
|
||||||
pages={pages}
|
pages={pages}
|
||||||
nextPage={nextPage}
|
nextPage={nextPage}
|
||||||
previousPage={previousPage}
|
previousPage={previousPage}
|
||||||
|
setPageNumber={setPageNumber}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -195,7 +216,7 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
onClick={toggleNavVisible}
|
onClick={toggleNavVisible}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
<span className="sr-only">{localize(lang, 'com_nav_open_sidebar')}</span>
|
<span className="sr-only">{localize('com_nav_open_sidebar')}</span>
|
||||||
<Panel open={true} />
|
<Panel open={true} />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { TMessage } from 'librechat-data-provider';
|
||||||
import { atom, selector } from 'recoil';
|
import { atom, selector } from 'recoil';
|
||||||
import { buildTree } from '~/utils';
|
import { buildTree } from '~/utils';
|
||||||
|
|
||||||
|
|
@ -11,7 +12,7 @@ const searchQuery = atom({
|
||||||
default: '',
|
default: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchResultMessages = atom({
|
const searchResultMessages = atom<TMessage[] | null>({
|
||||||
key: 'searchResultMessages',
|
key: 'searchResultMessages',
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -467,3 +467,11 @@ export type CleanupPreset = {
|
||||||
preset: Partial<TPreset>;
|
preset: Partial<TPreset>;
|
||||||
endpointsConfig?: TEndpointsConfig | Record<string, unknown>;
|
endpointsConfig?: TEndpointsConfig | Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PagesProps = {
|
||||||
|
pages: number;
|
||||||
|
pageNumber: number;
|
||||||
|
setPageNumber: (pageNumber: number) => void;
|
||||||
|
nextPage: () => Promise<void>;
|
||||||
|
previousPage: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue