From c7b586ba4cf475f1a5bf10f8e2b33871d56442a6 Mon Sep 17 00:00:00 2001 From: Danny Avila <110412045+danny-avila@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:51:23 -0400 Subject: [PATCH] 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 --- .../Conversations/{Pages.jsx => Pages.tsx} | 22 ++++-- .../src/components/Nav/{Nav.jsx => Nav.tsx} | 77 ++++++++++++------- client/src/store/search.ts | 3 +- packages/data-provider/src/types.ts | 8 ++ 4 files changed, 76 insertions(+), 34 deletions(-) rename client/src/components/Conversations/{Pages.jsx => Pages.tsx} (72%) rename client/src/components/Nav/{Nav.jsx => Nav.tsx} (77%) diff --git a/client/src/components/Conversations/Pages.jsx b/client/src/components/Conversations/Pages.tsx similarity index 72% rename from client/src/components/Conversations/Pages.jsx rename to client/src/components/Conversations/Pages.tsx index 754d45bbf..756438633 100644 --- a/client/src/components/Conversations/Pages.jsx +++ b/client/src/components/Conversations/Pages.tsx @@ -1,10 +1,22 @@ import React from 'react'; +import { PagesProps } from 'librechat-data-provider'; -export default function Pages({ pageNumber, pages, nextPage, previousPage }) { - const clickHandler = (func) => async (e) => { - e.preventDefault(); - await func(); - }; +export default function Pages({ + pageNumber, + pages, + nextPage, + previousPage, + setPageNumber, +}: PagesProps) { + const clickHandler = + (func: () => Promise) => async (e: React.MouseEvent) => { + e.preventDefault(); + await func(); + }; + + if (pageNumber > pages) { + setPageNumber(pages); + } return pageNumber == 1 && pages == 1 ? null : (
diff --git a/client/src/components/Nav/Nav.jsx b/client/src/components/Nav/Nav.tsx similarity index 77% rename from client/src/components/Nav/Nav.jsx rename to client/src/components/Nav/Nav.tsx index fe75c1332..97cd37e79 100644 --- a/client/src/components/Nav/Nav.jsx +++ b/client/src/components/Nav/Nav.tsx @@ -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 NavLinks from './NavLinks'; import { Panel, Spinner } from '~/components'; import { Conversations, Pages } from '../Conversations'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useGetConversationsQuery, useSearchQuery } from 'librechat-data-provider'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { useAuthContext, useDebounce } from '~/hooks'; -import { localize } from '~/localization/Translation'; +import { useAuthContext, useDebounce, useMediaQuery, useLocalize } from '~/hooks'; import { cn } from '~/utils/'; import store from '~/store'; export default function Nav({ navVisible, setNavVisible }) { const [isHovering, setIsHovering] = useState(false); + const [navWidth, setNavWidth] = useState('260px'); const { isAuthenticated } = useAuthContext(); - const containerRef = useRef(null); - const scrollPositionRef = useRef(null); - const lang = useRecoilValue(store.lang); + const containerRef = useRef(null); + const scrollPositionRef = useRef(null); + 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([]); // current page const [pageNumber, setPageNumber] = useState(1); // total pages const [pages, setPages] = useState(1); // data provider - const getConversationsQuery = useGetConversationsQuery(pageNumber, { enabled: isAuthenticated }); + const getConversationsQuery = useGetConversationsQuery(pageNumber + '', { + enabled: isAuthenticated, + }); // search const searchQuery = useRecoilValue(store.searchQuery); @@ -42,22 +58,28 @@ export default function Nav({ navVisible, setNavVisible }) { const [isFetching, setIsFetching] = useState(false); const debouncedSearchTerm = useDebounce(searchQuery, 750); - const searchQueryFn = useSearchQuery(debouncedSearchTerm, pageNumber, { - enabled: - !!debouncedSearchTerm && debouncedSearchTerm.length > 0 && isSearchEnabled && isSearching, + const searchQueryFn = useSearchQuery(debouncedSearchTerm, pageNumber + '', { + enabled: !!( + !!debouncedSearchTerm && + debouncedSearchTerm.length > 0 && + isSearchEnabled && + isSearching + ), }); - const onSearchSuccess = (data, expectedPage) => { + const onSearchSuccess = useCallback((data: TSearchResults, expectedPage?: number) => { const res = data; setConversations(res.conversations); if (expectedPage) { setPageNumber(expectedPage); } - setPages(res.pages); + setPages(Number(res.pages)); setIsFetching(false); searchPlaceholderConversation(); setSearchResultMessages(res.messages); - }; + /* disabled due recoil methods not recognized as state setters */ + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // Empty dependency array useEffect(() => { //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) { onSearchSuccess(searchQueryFn.data); } - }, [searchQueryFn.data, searchQueryFn.isInitialLoading]); + }, [searchQueryFn.data, searchQueryFn.isInitialLoading, onSearchSuccess]); const clearSearch = () => { setPageNumber(1); @@ -99,12 +121,13 @@ export default function Nav({ navVisible, setNavVisible }) { return; } let { conversations, pages } = getConversationsQuery.data; + pages = Number(pages); if (pageNumber > pages) { setPageNumber(pages); } else { if (!isSearching) { 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); @@ -121,7 +144,7 @@ export default function Nav({ navVisible, setNavVisible }) { }, [pageNumber, conversationId, refreshConversationsHint]); const toggleNavVisible = () => { - setNavVisible((prev) => !prev); + setNavVisible((prev: boolean) => !prev); }; const containerClasses = @@ -132,10 +155,11 @@ export default function Nav({ navVisible, setNavVisible }) { return ( <>
@@ -151,7 +175,7 @@ export default function Nav({ navVisible, setNavVisible }) { )} onClick={toggleNavVisible} > - {localize(lang, 'com_nav_close_sidebar')} + {localize('com_nav_close_sidebar')}
@@ -167,17 +191,14 @@ export default function Nav({ navVisible, setNavVisible }) { {(getConversationsQuery.isLoading && pageNumber === 1) || isFetching ? ( ) : ( - + )}
@@ -195,7 +216,7 @@ export default function Nav({ navVisible, setNavVisible }) { onClick={toggleNavVisible} >
- {localize(lang, 'com_nav_open_sidebar')} + {localize('com_nav_open_sidebar')}
diff --git a/client/src/store/search.ts b/client/src/store/search.ts index 95f22791a..e10f15b68 100644 --- a/client/src/store/search.ts +++ b/client/src/store/search.ts @@ -1,3 +1,4 @@ +import { TMessage } from 'librechat-data-provider'; import { atom, selector } from 'recoil'; import { buildTree } from '~/utils'; @@ -11,7 +12,7 @@ const searchQuery = atom({ default: '', }); -const searchResultMessages = atom({ +const searchResultMessages = atom({ key: 'searchResultMessages', default: null, }); diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index f2b81c2ec..23eac7f1f 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -467,3 +467,11 @@ export type CleanupPreset = { preset: Partial; endpointsConfig?: TEndpointsConfig | Record; }; + +export type PagesProps = { + pages: number; + pageNumber: number; + setPageNumber: (pageNumber: number) => void; + nextPage: () => Promise; + previousPage: () => Promise; +};