mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 11:50:14 +01:00
💡 style: switched to Ariakit's tooltip (#3748)
* inital Tooltip implementation and test * style(tooltip): L/R sidePanel and Nav * style(tooltip): unarchive button; refactor: `useArchiveHandler` and `ArchiveButton` * style(tooltip): Delete button * refactor: remove unused className prop in DeleteButton component * style(tooltip): finish final tooltip and fix bookmark edit and delete button * refactor(ui): remove TooltipTest and DropDownMenu component and unused imports * style: update mobile UI * fix: sidePanel icon not showing * feat(AttachFile): add tooltip * fix(NavToggle): remove button without this button, kb users don't have to manually press 2 times to change the focus Also, tooltips with buttons focus don't trigger * fix: right side panel issue with double button * fix: merge issues * fix: sharedLink table issue * chore: update ariakit and framer-motion version * a11y: kb toggle for sidebar * feat: tooltip for some buttons
This commit is contained in:
parent
e293ff63f9
commit
4ef5ae6f71
37 changed files with 747 additions and 967 deletions
|
|
@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react';
|
|||
import { Plus, X } from 'lucide-react';
|
||||
import { Transition } from 'react-transition-group';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface AssistantConversationStartersProps {
|
||||
|
|
@ -106,25 +106,19 @@ const AssistantConversationStarters: React.FC<AssistantConversationStartersProps
|
|||
}}
|
||||
className="absolute right-1 top-1"
|
||||
>
|
||||
<TooltipProvider delayDuration={1000}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onClick={handleAddStarter}
|
||||
disabled={hasReachedMax}
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{hasReachedMax
|
||||
? localize('com_assistants_max_starters_reached')
|
||||
: localize('com_ui_add')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipAnchor
|
||||
side="top"
|
||||
description={
|
||||
hasReachedMax
|
||||
? localize('com_assistants_max_starters_reached')
|
||||
: localize('com_ui_add')
|
||||
}
|
||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onClick={handleAddStarter}
|
||||
disabled={hasReachedMax}
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
</TooltipAnchor>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
|
|
@ -143,22 +137,14 @@ const AssistantConversationStarters: React.FC<AssistantConversationStartersProps
|
|||
type="text"
|
||||
maxLength={64}
|
||||
/>
|
||||
<TooltipProvider delayDuration={1000}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-1 top-1 flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onClick={() => handleDeleteStarter(index)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_delete')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipAnchor
|
||||
side="top"
|
||||
description={localize('com_ui_delete')}
|
||||
className="absolute right-1 top-1 flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onClick={() => handleDeleteStarter(index)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</TooltipAnchor>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import { useState } from 'react';
|
|||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||
import type { NavLink, NavProps } from '~/common';
|
||||
import { Accordion, AccordionItem, AccordionContent } from '~/components/ui/Accordion';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '~/components/ui/Tooltip';
|
||||
import { buttonVariants } from '~/components/ui/Button';
|
||||
import { cn, removeFocusOutlines } from '~/utils';
|
||||
import { TooltipAnchor } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function Nav({ links, isCollapsed, resize, defaultActive }: NavProps) {
|
||||
|
|
@ -30,42 +30,30 @@ export default function Nav({ links, isCollapsed, resize, defaultActive }: NavPr
|
|||
{links.map((link, index) => {
|
||||
const variant = getVariant(link);
|
||||
return isCollapsed ? (
|
||||
<Tooltip key={index} delayDuration={0}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
buttonVariants({ variant, size: 'icon' }),
|
||||
removeFocusOutlines,
|
||||
'h-9 w-9',
|
||||
variant === 'default'
|
||||
? 'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white'
|
||||
: '',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (link.onClick) {
|
||||
link.onClick(e);
|
||||
setActive('');
|
||||
return;
|
||||
}
|
||||
setActive(link.id);
|
||||
resize && resize(25);
|
||||
}}
|
||||
>
|
||||
<link.icon className="h-4 w-4" />
|
||||
<span className="sr-only">{link.title}</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
sideOffset={10}
|
||||
className="flex items-center gap-4"
|
||||
>
|
||||
{localize(link.title)}
|
||||
{link.label && (
|
||||
<span className="text-muted-foreground ml-auto">{link.label}</span>
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<TooltipAnchor
|
||||
className={cn(
|
||||
buttonVariants({ variant, size: 'icon' }),
|
||||
removeFocusOutlines,
|
||||
'h-9 w-9 cursor-pointer',
|
||||
variant === 'default'
|
||||
? 'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted bg-surface-terniary dark:hover:text-white'
|
||||
: '',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (link.onClick) {
|
||||
link.onClick(e);
|
||||
setActive('');
|
||||
return;
|
||||
}
|
||||
setActive(link.id);
|
||||
resize && resize(25);
|
||||
}}
|
||||
description={localize(link.title)}
|
||||
side="left"
|
||||
>
|
||||
<link.icon className="h-4 w-4" />
|
||||
<span className="sr-only">{link.title}</span>
|
||||
</TooltipAnchor>
|
||||
) : (
|
||||
<Accordion
|
||||
key={index}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ import {
|
|||
import type { ImperativePanelHandle } from 'react-resizable-panels';
|
||||
import type { TEndpointsConfig } from 'librechat-data-provider';
|
||||
import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/components/ui/Resizable';
|
||||
import { TooltipProvider, Tooltip } from '~/components/ui/Tooltip';
|
||||
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
||||
import { useMediaQuery, useLocalStorage, useLocalize } from '~/hooks';
|
||||
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
||||
import NavToggle from '~/components/Nav/NavToggle';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import Switcher from './Switcher';
|
||||
|
|
@ -176,116 +175,109 @@ const SidePanel = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
onLayout={(sizes) => throttledSaveLayout(sizes)}
|
||||
className="transition-width relative h-full w-full flex-1 overflow-auto bg-white dark:bg-gray-800"
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
onLayout={(sizes) => throttledSaveLayout(sizes)}
|
||||
className="transition-width relative h-full w-full flex-1 overflow-auto bg-white dark:bg-gray-800"
|
||||
>
|
||||
<ResizablePanel
|
||||
defaultSize={currentLayout[0]}
|
||||
minSize={minSizeMain}
|
||||
order={1}
|
||||
id="messages-view"
|
||||
>
|
||||
<ResizablePanel
|
||||
defaultSize={currentLayout[0]}
|
||||
minSize={minSizeMain}
|
||||
order={1}
|
||||
id="messages-view"
|
||||
>
|
||||
{children}
|
||||
</ResizablePanel>
|
||||
{artifacts != null && (
|
||||
<>
|
||||
<ResizableHandleAlt withHandle className="ml-3 bg-border-medium dark:text-white" />
|
||||
<ResizablePanel
|
||||
defaultSize={currentLayout[1]}
|
||||
minSize={minSizeMain}
|
||||
order={2}
|
||||
id="artifacts-panel"
|
||||
>
|
||||
{artifacts}
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
<TooltipProvider delayDuration={400}>
|
||||
<Tooltip>
|
||||
<div
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
className="relative flex w-px items-center justify-center"
|
||||
>
|
||||
<NavToggle
|
||||
navVisible={!isCollapsed}
|
||||
isHovering={isHovering}
|
||||
onToggle={toggleNavVisible}
|
||||
setIsHovering={setIsHovering}
|
||||
className={cn(
|
||||
'fixed top-1/2',
|
||||
(isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse
|
||||
? 'mr-9'
|
||||
: 'mr-16',
|
||||
)}
|
||||
translateX={false}
|
||||
side="right"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
{(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && (
|
||||
<ResizableHandleAlt withHandle className="bg-transparent dark:text-white" />
|
||||
)}
|
||||
<ResizablePanel
|
||||
tagName="nav"
|
||||
id="controls-nav"
|
||||
order={artifacts != null ? 3 : 2}
|
||||
aria-label={localize('com_ui_controls')}
|
||||
role="region"
|
||||
collapsedSize={collapsedSize}
|
||||
defaultSize={currentLayout[currentLayout.length - 1]}
|
||||
collapsible={true}
|
||||
minSize={minSize}
|
||||
maxSize={40}
|
||||
ref={panelRef}
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
transition: 'width 0.2s ease, visibility 0s linear 0.2s',
|
||||
}}
|
||||
onExpand={() => {
|
||||
setIsCollapsed(false);
|
||||
localStorage.setItem('react-resizable-panels:collapsed', 'false');
|
||||
}}
|
||||
onCollapse={() => {
|
||||
setIsCollapsed(true);
|
||||
localStorage.setItem('react-resizable-panels:collapsed', 'true');
|
||||
}}
|
||||
{children}
|
||||
</ResizablePanel>
|
||||
{artifacts != null && (
|
||||
<>
|
||||
<ResizableHandleAlt withHandle className="ml-3 bg-border-medium dark:text-white" />
|
||||
<ResizablePanel
|
||||
defaultSize={currentLayout[1]}
|
||||
minSize={minSizeMain}
|
||||
order={2}
|
||||
id="artifacts-panel"
|
||||
>
|
||||
{artifacts}
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
className="relative flex w-px items-center justify-center"
|
||||
>
|
||||
<NavToggle
|
||||
navVisible={!isCollapsed}
|
||||
isHovering={isHovering}
|
||||
onToggle={toggleNavVisible}
|
||||
setIsHovering={setIsHovering}
|
||||
className={cn(
|
||||
'sidenav hide-scrollbar border-l border-border-light bg-surface-primary-alt transition-opacity',
|
||||
isCollapsed ? 'min-w-[50px]' : 'min-w-[340px] sm:min-w-[352px]',
|
||||
(isSmallScreen && isCollapsed && (minSize === 0 || collapsedSize === 0)) ||
|
||||
fullCollapse
|
||||
? 'hidden min-w-0'
|
||||
: 'opacity-100',
|
||||
'fixed top-1/2',
|
||||
(isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse
|
||||
? 'mr-9'
|
||||
: 'mr-16',
|
||||
)}
|
||||
>
|
||||
{interfaceConfig.modelSelect && (
|
||||
<div
|
||||
className={cn(
|
||||
'sticky left-0 right-0 top-0 z-[100] flex h-[52px] flex-wrap items-center justify-center bg-surface-primary-alt',
|
||||
isCollapsed ? 'h-[52px]' : 'px-2',
|
||||
)}
|
||||
>
|
||||
<Switcher
|
||||
isCollapsed={isCollapsed}
|
||||
endpointKeyProvided={keyProvided}
|
||||
endpoint={endpoint}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Nav
|
||||
resize={panelRef.current?.resize}
|
||||
isCollapsed={isCollapsed}
|
||||
defaultActive={defaultActive}
|
||||
links={Links}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</TooltipProvider>
|
||||
translateX={false}
|
||||
side="right"
|
||||
/>
|
||||
</div>
|
||||
{(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && (
|
||||
<ResizableHandleAlt withHandle className="bg-transparent dark:text-white" />
|
||||
)}
|
||||
<ResizablePanel
|
||||
tagName="nav"
|
||||
id="controls-nav"
|
||||
order={artifacts != null ? 3 : 2}
|
||||
aria-label={localize('com_ui_controls')}
|
||||
role="region"
|
||||
collapsedSize={collapsedSize}
|
||||
defaultSize={currentLayout[currentLayout.length - 1]}
|
||||
collapsible={true}
|
||||
minSize={minSize}
|
||||
maxSize={40}
|
||||
ref={panelRef}
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
transition: 'width 0.2s ease, visibility 0s linear 0.2s',
|
||||
}}
|
||||
onExpand={() => {
|
||||
setIsCollapsed(false);
|
||||
localStorage.setItem('react-resizable-panels:collapsed', 'false');
|
||||
}}
|
||||
onCollapse={() => {
|
||||
setIsCollapsed(true);
|
||||
localStorage.setItem('react-resizable-panels:collapsed', 'true');
|
||||
}}
|
||||
className={cn(
|
||||
'sidenav hide-scrollbar border-l border-border-light bg-surface-primary-alt transition-opacity',
|
||||
isCollapsed ? 'min-w-[50px]' : 'min-w-[340px] sm:min-w-[352px]',
|
||||
(isSmallScreen && isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse
|
||||
? 'hidden min-w-0'
|
||||
: 'opacity-100',
|
||||
)}
|
||||
>
|
||||
{interfaceConfig.modelSelect && (
|
||||
<div
|
||||
className={cn(
|
||||
'sticky left-0 right-0 top-0 z-[100] flex h-[52px] flex-wrap items-center justify-center bg-surface-primary-alt',
|
||||
isCollapsed ? 'h-[52px]' : 'px-2',
|
||||
)}
|
||||
>
|
||||
<Switcher
|
||||
isCollapsed={isCollapsed}
|
||||
endpointKeyProvided={keyProvided}
|
||||
endpoint={endpoint}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Nav
|
||||
resize={panelRef.current?.resize}
|
||||
isCollapsed={isCollapsed}
|
||||
defaultActive={defaultActive}
|
||||
links={Links}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
<button
|
||||
aria-label="Close right side panel"
|
||||
className={`nav-mask ${!isCollapsed ? 'active' : ''}`}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue