mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-25 03:44:09 +01:00
🪄 refactor: UI Polish and Admin Dialog Unification (#11108)
* refactor(OpenSidebar): removed useless classNames
* style(Header): update hover styles across various components for improved UI consistency
* style(Nav): update hover styles in AccountSettings and SearchBar for improved UI consistency
* style: update button classes for consistent hover effects and improved UI responsiveness
* style(Nav, OpenSidebar, Header, Convo): improve UI responsiveness and animation transitions
* style(PresetsMenu, NewChat): update icon sizes and improve component styling for better UI consistency
* style(Nav, Root): enhance sidebar mobile animations and responsiveness for better UI experience
* style(ExportAndShareMenu, BookmarkMenu): update icon sizes for improved UI consistency
* style: remove transition duration from button classes for improved UI responsiveness
* style(CustomMenu, ModelSelector): update background colors for improved UI consistency and responsiveness
* style(ExportAndShareMenu): update icon color for improved UI consistency
* style(TemporaryChat): refine button styles for improved UI consistency and responsiveness
* style(BookmarkNav): refactor to use DropdownPopup and remove BookmarkNavItems for improved UI consistency and functionality
* style(CustomMenu, EndpointItem): enhance UI elements for improved consistency and accessibility
* style(EndpointItem): adjust gap in icon container for improved layout consistency
* style(CustomMenu, EndpointItem): update focus ring color for improved UI consistency
* style(EndpointItem): update icon color for improved UI consistency in dark theme
* style: update focus styles for improved accessibility and consistency across components
* refactor(Nav): extract sidebar width to NAV_WIDTH constant
Centralize mobile (320px) and desktop (260px) sidebar widths in a single
exported constant to avoid magic numbers and ensure consistency.
* fix(BookmarkNav): memoize handlers used in useMemo
Wrap handleTagClick and handleClear in useCallback and add them to the
dropdownItems useMemo dependency array to prevent stale closures.
* feat: introduce FilterInput component and replace existing inputs with it across multiple components
* feat(DataTable): replace custom input with FilterInput component for improved filtering
* fix: Nested dialog overlay stacking issue
Fixes overlay appearing behind content when opening nested dialogs.
Introduced dynamic z-index calculation based on dialog depth using React context.
- First dialog: overlay z-50, content z-100
- Nested dialogs increment by 60: overlay z-110/content z-160, etc.
Preserves a11y escape key handling from #10975 and #10851.
Regression from #11008 (afb67fcf1) which increased content z-index
without adjusting overlay z-index for nested dialog scenarios.
* Refactor admin settings components to use a unified AdminSettingsDialog
- Removed redundant code from AdminSettings, MCPAdminSettings, and Memories AdminSettings components.
- Introduced AdminSettingsDialog component to handle permission management for different sections.
- Updated permission handling logic to use a consistent structure across components.
- Enhanced role selection and permission confirmation features in the new dialog.
- Improved UI consistency and maintainability by centralizing dialog functionality.
* refactor(Memory): memory management UI components and replace MemoryViewer with MemoryPanel
* refactor(Memory): enhance UI components for Memory dialogs and improve input styling
* refactor(Bookmarks): improve bookmark management UI with enhanced styling
* refactor(translations): remove redundant filter input and bookmark count entries
* refactor(Convo): integrate useShiftKey hook for enhanced keyboard interaction and improve UI responsiveness
This commit is contained in:
parent
c21733930c
commit
5181356bef
71 changed files with 2115 additions and 2191 deletions
|
|
@ -25,7 +25,7 @@ function AccountSettings() {
|
|||
ref={accountSettingsButtonRef}
|
||||
aria-label={localize('com_nav_account_settings')}
|
||||
data-testid="nav-user"
|
||||
className="mt-text-sm flex h-auto w-full items-center gap-2 rounded-xl p-2 text-sm transition-all duration-200 ease-in-out hover:bg-surface-hover aria-[expanded=true]:bg-surface-hover"
|
||||
className="mt-text-sm flex h-auto w-full items-center gap-2 rounded-xl p-2 text-sm transition-all duration-200 ease-in-out hover:bg-surface-active-alt aria-[expanded=true]:bg-surface-active-alt"
|
||||
>
|
||||
<div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
|
|
@ -40,7 +40,7 @@ function AccountSettings() {
|
|||
</div>
|
||||
</Select.Select>
|
||||
<Select.SelectPopover
|
||||
className="popover-ui w-[305px] rounded-lg md:w-[235px]"
|
||||
className="popover-ui w-[305px] rounded-lg md:w-[244px]"
|
||||
style={{
|
||||
transformOrigin: 'bottom',
|
||||
translate: '0 -4px',
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useMemo } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { TooltipAnchor } from '@librechat/client';
|
||||
import { Menu, MenuButton, MenuItems } from '@headlessui/react';
|
||||
import { useState, useId, useMemo, useCallback } from 'react';
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import { CrossCircledIcon } from '@radix-ui/react-icons';
|
||||
import { DropdownPopup, TooltipAnchor } from '@librechat/client';
|
||||
import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons';
|
||||
import { BookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import type * as t from '~/common';
|
||||
import type { FC } from 'react';
|
||||
import { useGetConversationTags } from '~/data-provider';
|
||||
import BookmarkNavItems from './BookmarkNavItems';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -16,56 +16,105 @@ type BookmarkNavProps = {
|
|||
|
||||
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps) => {
|
||||
const localize = useLocalize();
|
||||
const menuId = useId();
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const { data } = useGetConversationTags();
|
||||
|
||||
const label = useMemo(
|
||||
() => (tags.length > 0 ? tags.join(', ') : localize('com_ui_bookmarks')),
|
||||
[tags, localize],
|
||||
);
|
||||
|
||||
const bookmarks = useMemo(() => data?.filter((tag) => tag.count > 0) ?? [], [data]);
|
||||
|
||||
const handleTagClick = useCallback(
|
||||
(tag: string) => {
|
||||
if (tags.includes(tag)) {
|
||||
setTags(tags.filter((t) => t !== tag));
|
||||
} else {
|
||||
setTags([...tags, tag]);
|
||||
}
|
||||
},
|
||||
[tags, setTags],
|
||||
);
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
setTags([]);
|
||||
}, [setTags]);
|
||||
|
||||
const dropdownItems: t.MenuItemProps[] = useMemo(() => {
|
||||
const items: t.MenuItemProps[] = [
|
||||
{
|
||||
id: 'clear-all',
|
||||
label: localize('com_ui_clear_all'),
|
||||
icon: <CrossCircledIcon className="size-4" />,
|
||||
hideOnClick: false,
|
||||
onClick: handleClear,
|
||||
},
|
||||
];
|
||||
|
||||
if (bookmarks.length === 0) {
|
||||
items.push({
|
||||
id: 'no-bookmarks',
|
||||
label: localize('com_ui_no_bookmarks'),
|
||||
icon: '🤔',
|
||||
disabled: true,
|
||||
});
|
||||
} else {
|
||||
for (const bookmark of bookmarks) {
|
||||
const isSelected = tags.includes(bookmark.tag);
|
||||
items.push({
|
||||
id: bookmark.tag,
|
||||
label: bookmark.tag,
|
||||
hideOnClick: false,
|
||||
icon: isSelected ? (
|
||||
<BookmarkFilledIcon className="size-4" />
|
||||
) : (
|
||||
<BookmarkIcon className="size-4" />
|
||||
),
|
||||
onClick: () => handleTagClick(bookmark.tag),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [bookmarks, tags, localize, handleTagClick, handleClear]);
|
||||
|
||||
return (
|
||||
<Menu as="div" className="group relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<TooltipAnchor
|
||||
description={label}
|
||||
render={
|
||||
<MenuButton
|
||||
id="bookmark-menu-button"
|
||||
aria-label={localize('com_ui_bookmarks')}
|
||||
className={cn(
|
||||
'flex items-center justify-center',
|
||||
'size-10 border-none text-text-primary hover:bg-accent hover:text-accent-foreground',
|
||||
'rounded-full border-none p-2 hover:bg-surface-hover md:rounded-xl',
|
||||
open ? 'bg-surface-hover' : '',
|
||||
)}
|
||||
data-testid="bookmark-menu"
|
||||
>
|
||||
{tags.length > 0 ? (
|
||||
<BookmarkFilledIcon aria-hidden="true" className="icon-lg text-text-primary" />
|
||||
) : (
|
||||
<BookmarkIcon aria-hidden="true" className="icon-lg text-text-primary" />
|
||||
)}
|
||||
</MenuButton>
|
||||
}
|
||||
/>
|
||||
<MenuItems
|
||||
anchor="bottom"
|
||||
className="absolute left-0 top-full z-[100] mt-1 w-60 translate-y-0 overflow-hidden rounded-lg bg-surface-secondary p-1.5 shadow-lg outline-none"
|
||||
>
|
||||
{data && (
|
||||
<BookmarkContext.Provider value={{ bookmarks: data.filter((tag) => tag.count > 0) }}>
|
||||
<BookmarkNavItems
|
||||
// List of selected tags(string)
|
||||
tags={tags}
|
||||
// When a user selects a tag, this `setTags` function is called to refetch the list of conversations for the selected tag
|
||||
setTags={setTags}
|
||||
/>
|
||||
</BookmarkContext.Provider>
|
||||
)}
|
||||
</MenuItems>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
<DropdownPopup
|
||||
portal={true}
|
||||
menuId={menuId}
|
||||
focusLoop={true}
|
||||
isOpen={isMenuOpen}
|
||||
unmountOnHide={true}
|
||||
setIsOpen={setIsMenuOpen}
|
||||
keyPrefix="bookmark-nav-"
|
||||
trigger={
|
||||
<TooltipAnchor
|
||||
description={label}
|
||||
render={
|
||||
<Ariakit.MenuButton
|
||||
id="bookmark-nav-menu-button"
|
||||
aria-label={localize('com_ui_bookmarks')}
|
||||
className={cn(
|
||||
'flex items-center justify-center',
|
||||
'size-10 border-none text-text-primary hover:bg-accent hover:text-accent-foreground',
|
||||
'rounded-full border-none p-2 hover:bg-surface-active-alt md:rounded-xl',
|
||||
isMenuOpen ? 'bg-surface-hover' : '',
|
||||
)}
|
||||
data-testid="bookmark-menu"
|
||||
>
|
||||
{tags.length > 0 ? (
|
||||
<BookmarkFilledIcon aria-hidden="true" className="icon-lg text-text-primary" />
|
||||
) : (
|
||||
<BookmarkIcon aria-hidden="true" className="icon-lg text-text-primary" />
|
||||
)}
|
||||
</Ariakit.MenuButton>
|
||||
}
|
||||
/>
|
||||
}
|
||||
items={dropdownItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
import { type FC } from 'react';
|
||||
import { CrossCircledIcon } from '@radix-ui/react-icons';
|
||||
import { useBookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import { BookmarkItems, BookmarkItem } from '~/components/Bookmarks';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const BookmarkNavItems: FC<{
|
||||
tags: string[];
|
||||
setTags: (tags: string[]) => void;
|
||||
}> = ({ tags = [], setTags }) => {
|
||||
const { bookmarks } = useBookmarkContext();
|
||||
const localize = useLocalize();
|
||||
|
||||
const getUpdatedSelected = (tag: string) => {
|
||||
if (tags.some((selectedTag) => selectedTag === tag)) {
|
||||
return tags.filter((selectedTag) => selectedTag !== tag);
|
||||
} else {
|
||||
return [...tags, tag];
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (tag?: string) => {
|
||||
if (tag === undefined) {
|
||||
return;
|
||||
}
|
||||
const updatedSelected = getUpdatedSelected(tag);
|
||||
setTags(updatedSelected);
|
||||
return;
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
setTags([]);
|
||||
return;
|
||||
};
|
||||
|
||||
if (bookmarks.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<BookmarkItem
|
||||
tag={localize('com_ui_clear_all')}
|
||||
data-testid="bookmark-item-clear"
|
||||
handleSubmit={clear}
|
||||
selected={false}
|
||||
icon={<CrossCircledIcon aria-hidden="true" className="size-4" />}
|
||||
/>
|
||||
<BookmarkItem
|
||||
tag={localize('com_ui_no_bookmarks')}
|
||||
data-testid="bookmark-item-no-bookmarks"
|
||||
handleSubmit={() => Promise.resolve()}
|
||||
selected={false}
|
||||
icon={'🤔'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<BookmarkItems
|
||||
tags={tags}
|
||||
handleSubmit={handleSubmit}
|
||||
header={
|
||||
<BookmarkItem
|
||||
tag={localize('com_ui_clear_all')}
|
||||
data-testid="bookmark-item-clear"
|
||||
handleSubmit={clear}
|
||||
selected={false}
|
||||
icon={<CrossCircledIcon aria-hidden="true" className="size-4" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookmarkNavItems;
|
||||
|
|
@ -21,7 +21,7 @@ export default function MobileNav({
|
|||
const { title = 'New Chat' } = conversation || {};
|
||||
|
||||
return (
|
||||
<div className="bg-token-main-surface-primary sticky top-0 z-10 flex min-h-[40px] items-center justify-center bg-white pl-1 dark:bg-gray-800 dark:text-white md:hidden">
|
||||
<div className="bg-token-main-surface-primary sticky top-0 z-10 flex min-h-[40px] items-center justify-center bg-presentation pl-1 dark:text-white md:hidden">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="mobile-header-new-chat-button"
|
||||
|
|
@ -29,7 +29,7 @@ export default function MobileNav({
|
|||
navVisible ? localize('com_nav_close_sidebar') : localize('com_nav_open_sidebar')
|
||||
}
|
||||
aria-live="polite"
|
||||
className="m-1 inline-flex size-10 items-center justify-center rounded-full hover:bg-surface-hover"
|
||||
className="m-1 inline-flex size-10 items-center justify-center rounded-full hover:bg-surface-active-alt"
|
||||
onClick={() =>
|
||||
setNavVisible((prev) => {
|
||||
localStorage.setItem('navVisible', JSON.stringify(!prev));
|
||||
|
|
@ -62,7 +62,7 @@ export default function MobileNav({
|
|||
<button
|
||||
type="button"
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
className="m-1 inline-flex size-10 items-center justify-center rounded-full hover:bg-surface-hover"
|
||||
className="m-1 inline-flex size-10 items-center justify-center rounded-full hover:bg-surface-active-alt"
|
||||
onClick={() => {
|
||||
clearMessagesCache(queryClient, conversation?.conversationId);
|
||||
queryClient.invalidateQueries([QueryKeys.messages]);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
import { useCallback, useEffect, useState, useMemo, memo, lazy, Suspense, useRef } from 'react';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
useMemo,
|
||||
memo,
|
||||
lazy,
|
||||
Suspense,
|
||||
useRef,
|
||||
startTransition,
|
||||
} from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Skeleton, useMediaQuery } from '@librechat/client';
|
||||
import { PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import type { InfiniteQueryObserverResult } from '@tanstack/react-query';
|
||||
|
|
@ -23,8 +33,10 @@ import store from '~/store';
|
|||
const BookmarkNav = lazy(() => import('./Bookmarks/BookmarkNav'));
|
||||
const AccountSettings = lazy(() => import('./AccountSettings'));
|
||||
|
||||
const NAV_WIDTH_DESKTOP = '260px';
|
||||
const NAV_WIDTH_MOBILE = '320px';
|
||||
export const NAV_WIDTH = {
|
||||
MOBILE: 320,
|
||||
DESKTOP: 260,
|
||||
} as const;
|
||||
|
||||
const SearchBarSkeleton = memo(() => (
|
||||
<div className={cn('flex h-10 items-center py-2')}>
|
||||
|
|
@ -66,7 +78,6 @@ const Nav = memo(
|
|||
const { isAuthenticated } = useAuthContext();
|
||||
useTitleGeneration(isAuthenticated);
|
||||
|
||||
const [navWidth, setNavWidth] = useState(NAV_WIDTH_DESKTOP);
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
const [newUser, setNewUser] = useLocalStorage('newUser', true);
|
||||
const [isChatsExpanded, setIsChatsExpanded] = useLocalStorage('chatsExpanded', true);
|
||||
|
|
@ -122,13 +133,17 @@ const Nav = memo(
|
|||
}, [data]);
|
||||
|
||||
const toggleNavVisible = useCallback(() => {
|
||||
setNavVisible((prev: boolean) => {
|
||||
localStorage.setItem('navVisible', JSON.stringify(!prev));
|
||||
return !prev;
|
||||
// Use startTransition to mark this as a non-urgent update
|
||||
// This prevents blocking the main thread during the cascade of re-renders
|
||||
startTransition(() => {
|
||||
setNavVisible((prev: boolean) => {
|
||||
localStorage.setItem('navVisible', JSON.stringify(!prev));
|
||||
return !prev;
|
||||
});
|
||||
if (newUser) {
|
||||
setNewUser(false);
|
||||
}
|
||||
});
|
||||
if (newUser) {
|
||||
setNewUser(false);
|
||||
}
|
||||
}, [newUser, setNavVisible, setNewUser]);
|
||||
|
||||
const itemToggleNav = useCallback(() => {
|
||||
|
|
@ -143,9 +158,6 @@ const Nav = memo(
|
|||
if (savedNavVisible === null) {
|
||||
toggleNavVisible();
|
||||
}
|
||||
setNavWidth(NAV_WIDTH_MOBILE);
|
||||
} else {
|
||||
setNavWidth(NAV_WIDTH_DESKTOP);
|
||||
}
|
||||
}, [isSmallScreen, toggleNavVisible]);
|
||||
|
||||
|
|
@ -201,61 +213,90 @@ const Nav = memo(
|
|||
}
|
||||
}, [search.query, search.isTyping, isLoading, isFetching]);
|
||||
|
||||
// Always render sidebar to avoid mount/unmount costs
|
||||
// Use transform for GPU-accelerated animation (no layout thrashing)
|
||||
const sidebarWidth = isSmallScreen ? NAV_WIDTH.MOBILE : NAV_WIDTH.DESKTOP;
|
||||
|
||||
// Sidebar content (shared between mobile and desktop)
|
||||
const sidebarContent = (
|
||||
<div className="flex h-full flex-col">
|
||||
<nav
|
||||
id="chat-history-nav"
|
||||
aria-label={localize('com_ui_chat_history')}
|
||||
className="flex h-full flex-col px-2 pb-3.5"
|
||||
aria-hidden={!navVisible}
|
||||
>
|
||||
<div className="flex flex-1 flex-col overflow-hidden" ref={outerContainerRef}>
|
||||
<MemoNewChat
|
||||
subHeaders={subHeaders}
|
||||
toggleNav={toggleNavVisible}
|
||||
headerButtons={headerButtons}
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
<div className="flex min-h-0 flex-grow flex-col overflow-hidden">
|
||||
<Conversations
|
||||
conversations={conversations}
|
||||
moveToTop={moveToTop}
|
||||
toggleNav={itemToggleNav}
|
||||
containerRef={conversationsRef}
|
||||
loadMoreConversations={loadMoreConversations}
|
||||
isLoading={isFetchingNextPage || showLoading || isLoading}
|
||||
isSearchLoading={isSearchLoading}
|
||||
isChatsExpanded={isChatsExpanded}
|
||||
setIsChatsExpanded={setIsChatsExpanded}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Suspense fallback={<Skeleton className="mt-1 h-12 w-full rounded-xl" />}>
|
||||
<AccountSettings />
|
||||
</Suspense>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Mobile: Fixed positioned sidebar that slides over content
|
||||
// Uses CSS transitions (not Framer Motion) to sync perfectly with content animation
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
data-testid="nav"
|
||||
className={cn(
|
||||
'nav fixed left-0 top-0 z-[70] h-full bg-surface-primary-alt',
|
||||
navVisible && 'active',
|
||||
)}
|
||||
style={{
|
||||
width: sidebarWidth,
|
||||
transform: navVisible ? 'translateX(0)' : `translateX(-${sidebarWidth}px)`,
|
||||
transition: 'transform 0.2s ease-out',
|
||||
}}
|
||||
>
|
||||
{sidebarContent}
|
||||
</div>
|
||||
<NavMask navVisible={navVisible} toggleNavVisible={toggleNavVisible} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Desktop: Inline sidebar with width transition
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence initial={false}>
|
||||
{navVisible && (
|
||||
<motion.div
|
||||
data-testid="nav"
|
||||
className={cn(
|
||||
'nav active max-w-[320px] flex-shrink-0 overflow-x-hidden bg-surface-primary-alt',
|
||||
'md:max-w-[260px]',
|
||||
)}
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: navWidth }}
|
||||
exit={{ width: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
key="nav"
|
||||
>
|
||||
<div className="h-full w-[320px] md:w-[260px]">
|
||||
<div className="flex h-full flex-col">
|
||||
<nav
|
||||
id="chat-history-nav"
|
||||
aria-label={localize('com_ui_chat_history')}
|
||||
className="flex h-full flex-col px-2 pb-3.5"
|
||||
>
|
||||
<div className="flex flex-1 flex-col overflow-hidden" ref={outerContainerRef}>
|
||||
<MemoNewChat
|
||||
subHeaders={subHeaders}
|
||||
toggleNav={toggleNavVisible}
|
||||
headerButtons={headerButtons}
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
<div className="flex min-h-0 flex-grow flex-col overflow-hidden">
|
||||
<Conversations
|
||||
conversations={conversations}
|
||||
moveToTop={moveToTop}
|
||||
toggleNav={itemToggleNav}
|
||||
containerRef={conversationsRef}
|
||||
loadMoreConversations={loadMoreConversations}
|
||||
isLoading={isFetchingNextPage || showLoading || isLoading}
|
||||
isSearchLoading={isSearchLoading}
|
||||
isChatsExpanded={isChatsExpanded}
|
||||
setIsChatsExpanded={setIsChatsExpanded}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Suspense fallback={<Skeleton className="mt-1 h-12 w-full rounded-xl" />}>
|
||||
<AccountSettings />
|
||||
</Suspense>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{isSmallScreen && <NavMask navVisible={navVisible} toggleNavVisible={toggleNavVisible} />}
|
||||
</>
|
||||
<div
|
||||
className="flex-shrink-0 overflow-hidden"
|
||||
style={{ width: navVisible ? sidebarWidth : 0, transition: 'width 0.2s ease-out' }}
|
||||
>
|
||||
<motion.div
|
||||
data-testid="nav"
|
||||
className={cn('nav h-full bg-surface-primary-alt', navVisible && 'active')}
|
||||
style={{ width: sidebarWidth }}
|
||||
initial={false}
|
||||
animate={{
|
||||
x: navVisible ? 0 : -sidebarWidth,
|
||||
}}
|
||||
transition={{ duration: 0.2, ease: 'easeOut' }}
|
||||
>
|
||||
{sidebarContent}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -66,13 +66,13 @@ export default function NewChat({
|
|||
data-testid="close-sidebar-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"
|
||||
className="rounded-full border-none bg-transparent duration-0 hover:bg-surface-active-alt md:rounded-xl"
|
||||
onClick={handleToggleNav}
|
||||
>
|
||||
<Sidebar aria-hidden="true" className="max-md:hidden" />
|
||||
<MobileSidebar
|
||||
aria-hidden="true"
|
||||
className="m-1 inline-flex size-10 items-center justify-center md:hidden"
|
||||
className="icon-lg m-1 inline-flex items-center justify-center md:hidden"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ export default function NewChat({
|
|||
variant="outline"
|
||||
data-testid="nav-new-chat-button"
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
className="rounded-full border-none bg-transparent p-2 hover:bg-surface-hover md:rounded-xl"
|
||||
className="rounded-full border-none bg-transparent duration-0 hover:bg-surface-active-alt md:rounded-xl"
|
||||
onClick={clickHandler}
|
||||
>
|
||||
<NewChatIcon className="icon-lg text-text-primary" />
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: React.Ref<HTMLDivEleme
|
|||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="group relative my-1 flex h-10 cursor-pointer items-center gap-3 rounded-lg border-2 border-transparent px-3 py-2 text-text-primary focus-within:border-ring-primary focus-within:bg-surface-hover hover:bg-surface-hover"
|
||||
className="group relative my-1 flex h-10 cursor-pointer items-center gap-3 rounded-lg border-2 border-transparent px-3 py-2 text-text-primary focus-within:border-ring-primary focus-within:bg-surface-active-alt hover:bg-surface-active-alt"
|
||||
>
|
||||
<Search
|
||||
aria-hidden="true"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export * from './ExportConversation';
|
||||
export * from './SettingsTabs/';
|
||||
export { default as MobileNav } from './MobileNav';
|
||||
export { default as Nav } from './Nav';
|
||||
export { default as Nav, NAV_WIDTH } from './Nav';
|
||||
export { default as NavLink } from './NavLink';
|
||||
export { default as NewChat } from './NewChat';
|
||||
export { default as SearchBar } from './SearchBar';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue