mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 02:10:15 +01:00
👐 style: Improve a11y/theming for Settings Dialog, Dropdown Menus; fix: SearchBar focus issues (#4091)
* fix: cursor pointer not applying correct in the root component * fix: add cursor-not-allowed to disabled state in SendButton component * feat: update Dropdown to ariakit and changed LLM error's style * feat: switched to ariakit's Dropdown and style improvements * feat: archive updates * refactor: delete conversations in archive * refactor: settings * add cool settings animation * a11y: settings update * style: update settings * style: settings account settings menu; a11y(AccountSettings): switched to AriaKit * a11y: account settings update * style: update my files dialog * fix: tests * chore: remove console.log() --------- Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
eba2c9a032
commit
2d62eca612
58 changed files with 1054 additions and 824 deletions
|
|
@ -1,23 +1,20 @@
|
|||
import { FileText } from 'lucide-react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import * as Select from '@ariakit/react/select';
|
||||
import { Fragment, useState, memo } from 'react';
|
||||
import { Menu, MenuItem, MenuButton, MenuItems, Transition } from '@headlessui/react';
|
||||
import { FileText, LogOut } from 'lucide-react';
|
||||
import { useGetUserBalance, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import { LinkIcon, GearIcon, DropdownMenuSeparator } from '~/components';
|
||||
import FilesView from '~/components/Chat/Input/Files/FilesView';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import useAvatar from '~/hooks/Messages/useAvatar';
|
||||
import { LinkIcon, GearIcon } from '~/components';
|
||||
import { UserIcon } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import Settings from './Settings';
|
||||
import NavLink from './NavLink';
|
||||
import Logout from './Logout';
|
||||
import { cn } from '~/utils/';
|
||||
import store from '~/store';
|
||||
|
||||
function AccountSettings() {
|
||||
const localize = useLocalize();
|
||||
const { user, isAuthenticated } = useAuthContext();
|
||||
const { user, isAuthenticated, logout } = useAuthContext();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const balanceQuery = useGetUserBalance({
|
||||
enabled: !!isAuthenticated && startupConfig?.checkBalance,
|
||||
|
|
@ -29,115 +26,105 @@ function AccountSettings() {
|
|||
const name = user?.avatar ?? user?.username ?? '';
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menu as="div" className="group relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<MenuButton
|
||||
aria-label={localize('com_nav_account_settings')}
|
||||
className={cn(
|
||||
'group-ui-open:bg-surface-tertiary duration-350 mt-text-sm flex h-auto w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors hover:bg-surface-secondary',
|
||||
open ? 'bg-surface-secondary' : '',
|
||||
)}
|
||||
data-testid="nav-user"
|
||||
>
|
||||
<div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
{name.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'rgb(121, 137, 255)',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
boxShadow: 'rgba(240, 246, 252, 0.1) 0px 0px 0px 1px',
|
||||
}}
|
||||
className="relative flex items-center justify-center rounded-full p-1 text-text-primary"
|
||||
>
|
||||
<UserIcon />
|
||||
</div>
|
||||
) : (
|
||||
<img className="rounded-full" src={user?.avatar ?? avatarSrc} alt="avatar" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Select.SelectProvider>
|
||||
<Select.Select
|
||||
aria-label={localize('com_nav_account_settings')}
|
||||
data-testid="nav-user"
|
||||
className="duration-350 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-accent"
|
||||
>
|
||||
<div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
{name.length === 0 ? (
|
||||
<div
|
||||
className="mt-2 grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-text-primary"
|
||||
style={{ marginTop: '0', marginLeft: '0' }}
|
||||
style={{
|
||||
backgroundColor: 'rgb(121, 137, 255)',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
boxShadow: 'rgba(240, 246, 252, 0.1) 0px 0px 0px 1px',
|
||||
}}
|
||||
className="relative flex items-center justify-center rounded-full p-1 text-text-primary"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{user?.name ?? user?.username ?? localize('com_nav_user')}
|
||||
<UserIcon />
|
||||
</div>
|
||||
</MenuButton>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100 transform"
|
||||
enterFrom="translate-y-2 opacity-0"
|
||||
enterTo="translate-y-0 opacity-100"
|
||||
leave="transition ease-in duration-100 transform"
|
||||
leaveFrom="translate-y-0 opacity-100"
|
||||
leaveTo="translate-y-2 opacity-0"
|
||||
>
|
||||
<MenuItems className="absolute bottom-full left-0 z-[100] mb-1 mt-1 w-full translate-y-0 overflow-hidden rounded-lg border border-border-medium bg-header-primary p-1.5 opacity-100 shadow-lg outline-none">
|
||||
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm" role="none">
|
||||
{user?.email ?? localize('com_nav_user')}
|
||||
</div>
|
||||
<div className="my-1.5 h-px border-b border-border-medium" role="none" />
|
||||
{startupConfig?.checkBalance === true &&
|
||||
balanceQuery.data != null &&
|
||||
!isNaN(parseFloat(balanceQuery.data)) && (
|
||||
<>
|
||||
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm">
|
||||
{`Balance: ${parseFloat(balanceQuery.data).toFixed(2)}`}
|
||||
</div>
|
||||
<div className="my-1.5 h-px border-b border-border-medium" role="none" />
|
||||
</>
|
||||
)}
|
||||
<MenuItem>
|
||||
{({ focus }) => (
|
||||
<NavLink
|
||||
className={focus ? 'bg-surface-hover' : ''}
|
||||
svg={() => <FileText className="icon-md" />}
|
||||
text={localize('com_nav_my_files')}
|
||||
clickHandler={() => setShowFiles(true)}
|
||||
/>
|
||||
)}
|
||||
</MenuItem>
|
||||
{startupConfig?.helpAndFaqURL !== '/' && (
|
||||
<MenuItem>
|
||||
{({ focus }) => (
|
||||
<NavLink
|
||||
className={focus ? 'bg-surface-hover' : ''}
|
||||
svg={() => <LinkIcon />}
|
||||
text={localize('com_nav_help_faq')}
|
||||
clickHandler={() => window.open(startupConfig?.helpAndFaqURL, '_blank')}
|
||||
/>
|
||||
)}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem>
|
||||
{({ focus }) => (
|
||||
<NavLink
|
||||
className={focus ? 'bg-surface-hover' : ''}
|
||||
svg={() => <GearIcon className="icon-md" />}
|
||||
text={localize('com_nav_settings')}
|
||||
clickHandler={() => {
|
||||
setTimeout(() => setShowSettings(true), 50);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MenuItem>
|
||||
<div className="my-1.5 h-px border-b border-border-medium" role="none" />
|
||||
<MenuItem>
|
||||
{({ focus }) => <Logout className={focus ? 'bg-surface-hover' : ''} />}
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</Transition>
|
||||
) : (
|
||||
<img
|
||||
className="rounded-full"
|
||||
src={user?.avatar ?? avatarSrc}
|
||||
alt={`${name}'s avatar`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mt-2 grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-text-primary"
|
||||
style={{ marginTop: '0', marginLeft: '0' }}
|
||||
>
|
||||
{user?.name ?? user?.username ?? localize('com_nav_user')}
|
||||
</div>
|
||||
</Select.Select>
|
||||
<Select.SelectPopover
|
||||
className="popover-ui w-[235px]"
|
||||
style={{
|
||||
transformOrigin: 'bottom',
|
||||
marginRight: '0px',
|
||||
translate: '0px',
|
||||
}}
|
||||
>
|
||||
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm" role="note">
|
||||
{user?.email ?? localize('com_nav_user')}
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
{startupConfig?.checkBalance === true &&
|
||||
balanceQuery.data != null &&
|
||||
!isNaN(parseFloat(balanceQuery.data)) && (
|
||||
<>
|
||||
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm" role="note">
|
||||
{`Balance: ${parseFloat(balanceQuery.data).toFixed(2)}`}
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
<Select.SelectItem
|
||||
value=""
|
||||
onClick={() => setShowFiles(true)}
|
||||
className="select-item text-sm"
|
||||
>
|
||||
<FileText className="icon-md" aria-hidden="true" />
|
||||
{localize('com_nav_my_files')}
|
||||
</Select.SelectItem>
|
||||
{startupConfig?.helpAndFaqURL !== '/' && (
|
||||
<Select.SelectItem
|
||||
value=""
|
||||
onClick={() => window.open(startupConfig?.helpAndFaqURL, '_blank')}
|
||||
className="select-item text-sm"
|
||||
>
|
||||
<LinkIcon aria-hidden="true" />
|
||||
{localize('com_nav_help_faq')}
|
||||
</Select.SelectItem>
|
||||
)}
|
||||
<Select.SelectItem
|
||||
value=""
|
||||
onClick={() => setShowSettings(true)}
|
||||
className="select-item text-sm"
|
||||
>
|
||||
<GearIcon className="icon-md" aria-hidden="true" />
|
||||
{localize('com_nav_settings')}
|
||||
</Select.SelectItem>
|
||||
<DropdownMenuSeparator />
|
||||
<Select.SelectItem
|
||||
aria-selected={true}
|
||||
onClick={() => logout()}
|
||||
value="logout"
|
||||
className="select-item text-sm"
|
||||
>
|
||||
<LogOut className="icon-md" />
|
||||
{localize('com_nav_log_out')}
|
||||
</Select.SelectItem>
|
||||
</Select.SelectPopover>
|
||||
{showFiles && <FilesView open={showFiles} onOpenChange={setShowFiles} />}
|
||||
{showSettings && <Settings open={showSettings} onOpenChange={setShowSettings} />}
|
||||
</>
|
||||
</Select.SelectProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue