mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-06 18:48:50 +01:00
🔧 fix+chore: Resolve Overflow in Settings Modal & Upgrade to Headless UI 2.0 (#2661)
* fix: dropdown overflow * fix: make dropdown work on mobile * feat: update headlessui to 2.0 and use portal * feat: rewrite modal using headlessui * fix: applying of maxHeight * fix: optimize backdrop for dark mode * fix: rendering dropdown width * feat: match small screen layout to radix-ui dialog * revert: mobile modifications * fix: modal animations * fix: z-index * chore: Migrate from HeadlessUI 1.0 to 2.0 * fix: h2 nesting * fix: use lighter border for PopoverButtons * feat: Move modal to the top if using a small screen * fix: mobile position * fix: frontend tests * feat: use row layout in mobile instead of col * fix: remove config path from tsconfig * fix: fix dropdown tests (gpt4o ftw!) * feat: Upgrade to latest headlessui version * fix:test1 * fix: ThemeSelector test * fix: re-add speech tab * style: use pl and pr-3 * fix: speech tab dropdowns * style: use maxHeight for language * feat: convert DropdownNoState to v2.0 * fix: use v2 params for voiceDropdown * style: reduce maxHeight for VoiceDropdown and set fixed width * chore: rebuild package-lock * style(fix): copy over the same styles for the settingsTab * style(fix): use -top-1 for speech tabs * style(fix): use max-w-[400px] --------- Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
b34a4ddac1
commit
03fe361917
23 changed files with 573 additions and 328 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { FileText } from 'lucide-react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Fragment, useState, memo } from 'react';
|
||||
import { Menu, Transition } from '@headlessui/react';
|
||||
import { Menu, MenuItem, MenuButton, MenuItems, Transition } from '@headlessui/react';
|
||||
import { useGetUserBalance, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import FilesView from '~/components/Chat/Input/Files/FilesView';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
|
|
@ -32,7 +32,7 @@ function NavLinks() {
|
|||
<Menu as="div" className="group relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button
|
||||
<MenuButton
|
||||
className={cn(
|
||||
'group-ui-open:bg-gray-100 dark:group-ui-open:bg-gray-700 duration-350 mt-text-sm flex h-auto w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-800',
|
||||
open ? 'bg-gray-100 dark:bg-gray-800' : '',
|
||||
|
|
@ -64,7 +64,7 @@ function NavLinks() {
|
|||
>
|
||||
{user?.name || user?.username || localize('com_nav_user')}
|
||||
</div>
|
||||
</Menu.Button>
|
||||
</MenuButton>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
|
|
@ -75,7 +75,7 @@ function NavLinks() {
|
|||
leaveFrom="translate-y-0 opacity-100"
|
||||
leaveTo="translate-y-2 opacity-0"
|
||||
>
|
||||
<Menu.Items className="absolute bottom-full left-0 z-[100] mb-1 mt-1 w-full translate-y-0 overflow-hidden rounded-lg border border-gray-300 bg-white p-1.5 opacity-100 shadow-lg outline-none dark:border-gray-600 dark:bg-gray-700">
|
||||
<MenuItems className="absolute bottom-full left-0 z-[100] mb-1 mt-1 w-full translate-y-0 overflow-hidden rounded-lg border border-gray-300 bg-white p-1.5 opacity-100 shadow-lg outline-none dark:border-gray-600 dark:bg-gray-700">
|
||||
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm" role="none">
|
||||
{user?.email || localize('com_nav_user')}
|
||||
</div>
|
||||
|
|
@ -90,34 +90,34 @@ function NavLinks() {
|
|||
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
||||
</>
|
||||
)}
|
||||
<Menu.Item as="div">
|
||||
<MenuItem as="div">
|
||||
<NavLink
|
||||
svg={() => <FileText className="icon-md" />}
|
||||
text={localize('com_nav_my_files')}
|
||||
clickHandler={() => setShowFiles(true)}
|
||||
/>
|
||||
</Menu.Item>
|
||||
</MenuItem>
|
||||
{startupConfig?.helpAndFaqURL !== '/' && (
|
||||
<Menu.Item as="div">
|
||||
<MenuItem as="div">
|
||||
<NavLink
|
||||
svg={() => <LinkIcon />}
|
||||
text={localize('com_nav_help_faq')}
|
||||
clickHandler={() => window.open(startupConfig?.helpAndFaqURL, '_blank')}
|
||||
/>
|
||||
</Menu.Item>
|
||||
</MenuItem>
|
||||
)}
|
||||
<Menu.Item as="div">
|
||||
<MenuItem as="div">
|
||||
<NavLink
|
||||
svg={() => <GearIcon className="icon-md" />}
|
||||
text={localize('com_nav_settings')}
|
||||
clickHandler={() => setShowSettings(true)}
|
||||
/>
|
||||
</Menu.Item>
|
||||
</MenuItem>
|
||||
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
||||
<Menu.Item as="div">
|
||||
<MenuItem as="div">
|
||||
<Logout />
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@ import * as Tabs from '@radix-ui/react-tabs';
|
|||
import { MessageSquare } from 'lucide-react';
|
||||
import { SettingsTabValues } from 'librechat-data-provider';
|
||||
import type { TDialogProps } from '~/common';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
DialogTitle,
|
||||
Transition,
|
||||
TransitionChild,
|
||||
} from '@headlessui/react';
|
||||
import { GearIcon, DataIcon, SpeechIcon, UserIcon, ExperimentIcon } from '~/components/svg';
|
||||
import { General, Messages, Speech, Beta, Data, Account } from './SettingsTabs';
|
||||
import { useMediaQuery, useLocalize } from '~/hooks';
|
||||
|
|
@ -13,132 +20,183 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
|||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
disableScroll={isSmallScreen}
|
||||
className={cn(
|
||||
'max-h-[90vh] overflow-auto shadow-2xl md:min-h-[500px] md:w-[680px]',
|
||||
isSmallScreen ? 'min-h-[200px]' : '',
|
||||
)}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
|
||||
{localize('com_nav_settings')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]">
|
||||
<Tabs.Root
|
||||
defaultValue={SettingsTabValues.GENERAL}
|
||||
className="flex flex-col gap-3 md:flex-row"
|
||||
orientation="horizontal"
|
||||
<Transition appear show={open}>
|
||||
<Dialog as="div" className="relative z-50 focus:outline-none" onClose={onOpenChange}>
|
||||
<TransitionChild
|
||||
enter="ease-out duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black/50 dark:bg-black/80" aria-hidden="true" />
|
||||
</TransitionChild>
|
||||
|
||||
<TransitionChild
|
||||
enter="ease-out duration-200"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-100"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'fixed inset-0 flex w-screen items-center justify-center p-4',
|
||||
isSmallScreen ? '' : '',
|
||||
)}
|
||||
>
|
||||
<Tabs.List
|
||||
aria-label="Settings"
|
||||
role="tablist"
|
||||
aria-orientation="horizontal"
|
||||
<DialogPanel
|
||||
className={cn(
|
||||
isSmallScreen
|
||||
? 'hide-scrollbar flex flex-row space-x-4 overflow-x-auto'
|
||||
: 'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-col flex-wrap overflow-auto sm:max-w-none',
|
||||
'overflow-hidden rounded-xl rounded-b-lg bg-white pb-6 shadow-2xl backdrop-blur-2xl animate-in dark:bg-gray-700 sm:rounded-lg md:min-h-[373px] md:w-[680px]',
|
||||
)}
|
||||
style={{ outline: 'none' }}
|
||||
>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.GENERAL}
|
||||
style={{ userSelect: 'none' }}
|
||||
<DialogTitle
|
||||
className="mb-3 flex items-center justify-between border-b border-black/10 p-6 pb-5 text-left dark:border-white/10"
|
||||
as="div"
|
||||
>
|
||||
<GearIcon />
|
||||
{localize('com_nav_setting_general')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.MESSAGES}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<MessageSquare className="icon-sm" />
|
||||
{localize('com_endpoint_messages')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.BETA}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<ExperimentIcon />
|
||||
{localize('com_nav_setting_beta')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.SPEECH}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<SpeechIcon className="icon-sm" />
|
||||
{localize('com_nav_setting_speech')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.DATA}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<DataIcon />
|
||||
{localize('com_nav_setting_data')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black transition-all duration-200 ease-in-out radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
||||
: 'bg-white radix-state-active:bg-gray-100',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.ACCOUNT}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<UserIcon />
|
||||
{localize('com_nav_setting_account')}
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<div className="h-auto min-h-[280px] overflow-auto sm:w-full sm:max-w-none">
|
||||
<General />
|
||||
<Messages />
|
||||
<Beta />
|
||||
<Speech />
|
||||
<Data />
|
||||
<Account />
|
||||
</div>
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<h2 className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
|
||||
{localize('com_nav_settings')}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-gray-100 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900 dark:data-[state=open]:bg-gray-800"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-5 w-5 text-black dark:text-white"
|
||||
>
|
||||
<line x1="18" x2="6" y1="6" y2="18"></line>
|
||||
<line x1="6" x2="18" y1="6" y2="18"></line>
|
||||
</svg>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</DialogTitle>
|
||||
<div className="max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]">
|
||||
<Tabs.Root
|
||||
defaultValue={SettingsTabValues.GENERAL}
|
||||
className="flex flex-col gap-10 md:flex-row"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Tabs.List
|
||||
aria-label="Settings"
|
||||
role="tablist"
|
||||
aria-orientation="horizontal"
|
||||
className={cn(
|
||||
'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-nowrap overflow-auto sm:max-w-none',
|
||||
isSmallScreen ? 'flex-row rounded-lg bg-gray-200 p-1 dark:bg-gray-800' : '',
|
||||
)}
|
||||
style={{ outline: 'none' }}
|
||||
>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.GENERAL}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<GearIcon />
|
||||
{localize('com_nav_setting_general')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.MESSAGES}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<MessageSquare className="icon-sm" />
|
||||
{localize('com_endpoint_messages')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.BETA}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<ExperimentIcon />
|
||||
{localize('com_nav_setting_beta')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.SPEECH}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<SpeechIcon className="icon-sm" />
|
||||
{localize('com_nav_setting_speech')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.DATA}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<DataIcon />
|
||||
{localize('com_nav_setting_data')}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'group m-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-white radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-600',
|
||||
isSmallScreen
|
||||
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||
: 'bg-white radix-state-active:bg-gray-200',
|
||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||
)}
|
||||
value={SettingsTabValues.ACCOUNT}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
<UserIcon />
|
||||
{localize('com_nav_setting_account')}
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<div className="max-h-[373px] overflow-auto sm:w-full sm:max-w-none md:pr-0.5 md:pt-0.5">
|
||||
<General />
|
||||
<Messages />
|
||||
<Beta />
|
||||
<Speech />
|
||||
<Data />
|
||||
<Account />
|
||||
</div>
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,9 +34,8 @@ export const ThemeSelector = ({
|
|||
value={theme}
|
||||
onChange={onChange}
|
||||
options={themeOptions}
|
||||
width={180}
|
||||
position={'left'}
|
||||
maxHeight="200px"
|
||||
sizeClasses="w-[220px]"
|
||||
anchor="bottom start"
|
||||
testId="theme-selector"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -111,8 +110,8 @@ export const LangSelector = ({
|
|||
<Dropdown
|
||||
value={langcode}
|
||||
onChange={onChange}
|
||||
position={'left'}
|
||||
maxHeight="271px"
|
||||
sizeClasses="[--anchor-max-height:256px]"
|
||||
anchor="bottom start"
|
||||
options={languageOptions}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ describe('LangSelector', () => {
|
|||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
global.ResizeObserver = class MockedResizeObserver {
|
||||
observe = jest.fn();
|
||||
unobserve = jest.fn();
|
||||
disconnect = jest.fn();
|
||||
};
|
||||
const { getByText } = render(
|
||||
<RecoilRoot>
|
||||
<LangSelector langcode="en-US" onChange={mockOnChange} />
|
||||
|
|
@ -24,6 +29,11 @@ describe('LangSelector', () => {
|
|||
});
|
||||
|
||||
it('calls onChange when the select value changes', async () => {
|
||||
global.ResizeObserver = class MockedResizeObserver {
|
||||
observe = jest.fn();
|
||||
unobserve = jest.fn();
|
||||
disconnect = jest.fn();
|
||||
};
|
||||
const { getByText, getByTestId } = render(
|
||||
<RecoilRoot>
|
||||
<LangSelector langcode="en-US" onChange={mockOnChange} />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// ThemeSelector.spec.tsx
|
||||
import 'test/matchMedia.mock';
|
||||
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { render, fireEvent, waitFor } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { ThemeSelector } from './General';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
|
@ -13,6 +15,11 @@ describe('ThemeSelector', () => {
|
|||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
global.ResizeObserver = class MockedResizeObserver {
|
||||
observe = jest.fn();
|
||||
unobserve = jest.fn();
|
||||
disconnect = jest.fn();
|
||||
};
|
||||
const { getByText } = render(
|
||||
<RecoilRoot>
|
||||
<ThemeSelector theme="system" onChange={mockOnChange} />
|
||||
|
|
@ -24,6 +31,11 @@ describe('ThemeSelector', () => {
|
|||
});
|
||||
|
||||
it('calls onChange when the select value changes', async () => {
|
||||
global.ResizeObserver = class MockedResizeObserver {
|
||||
observe = jest.fn();
|
||||
unobserve = jest.fn();
|
||||
disconnect = jest.fn();
|
||||
};
|
||||
const { getByText, getByTestId } = render(
|
||||
<RecoilRoot>
|
||||
<ThemeSelector theme="system" onChange={mockOnChange} />
|
||||
|
|
@ -42,9 +54,9 @@ describe('ThemeSelector', () => {
|
|||
const darkOption = getByText('Dark');
|
||||
fireEvent.click(darkOption);
|
||||
|
||||
// Ensure that the onChange is called with the expected value after a short delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith('dark');
|
||||
// Ensure that the onChange is called with the expected value
|
||||
await waitFor(() => {
|
||||
expect(mockOnChange).toHaveBeenCalledWith('dark');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ export const ForkSettings = () => {
|
|||
value={forkSetting}
|
||||
onChange={setForkSetting}
|
||||
options={forkOptions}
|
||||
position={'left'}
|
||||
maxHeight="199px"
|
||||
sizeClasses="w-[200px]"
|
||||
anchor="bottom start"
|
||||
testId="fork-setting-dropdown"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ export default function EngineSTTDropdown() {
|
|||
value={engineSTT}
|
||||
onChange={handleSelect}
|
||||
options={endpointOptions}
|
||||
width={180}
|
||||
position={'left'}
|
||||
sizeClasses="w-[180px]"
|
||||
anchor="bottom start"
|
||||
testId="EngineSTTDropdown"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -98,8 +98,8 @@ export default function LanguageSTTDropdown() {
|
|||
value={languageSTT}
|
||||
onChange={handleSelect}
|
||||
options={languageOptions}
|
||||
width={220}
|
||||
position={'left'}
|
||||
sizeClasses="[--anchor-max-height:256px]"
|
||||
anchor="bottom start"
|
||||
testId="LanguageSTTDropdown"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ function Speech() {
|
|||
<Tabs.Content
|
||||
value={SettingsTabValues.SPEECH}
|
||||
role="tabpanel"
|
||||
className="w-full px-4 md:min-h-[300px]"
|
||||
className="w-full md:min-h-[271px]"
|
||||
ref={contentRef}
|
||||
>
|
||||
<Tabs.Root
|
||||
|
|
@ -138,8 +138,8 @@ function Speech() {
|
|||
orientation="horizontal"
|
||||
value={advancedMode ? 'advanced' : 'simple'}
|
||||
>
|
||||
<div className="sticky top-0 z-50 bg-white dark:bg-gray-700">
|
||||
<Tabs.List className="sticky top-0 mb-4 flex justify-center bg-white dark:bg-gray-700">
|
||||
<div className="sticky -top-1 z-50 mb-4 bg-white dark:bg-gray-700">
|
||||
<Tabs.List className="flex justify-center bg-white dark:bg-gray-700">
|
||||
<Tabs.Trigger
|
||||
onClick={() => setAdvancedMode(false)}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ export default function EngineTTSDropdown() {
|
|||
value={engineTTS}
|
||||
onChange={handleSelect}
|
||||
options={endpointOptions}
|
||||
width={180}
|
||||
position={'left'}
|
||||
sizeClasses="w-[180px]"
|
||||
anchor="bottom start"
|
||||
testId="EngineTTSDropdown"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ export default function VoiceDropdown() {
|
|||
value={voice}
|
||||
onChange={setVoice}
|
||||
options={voiceOptions}
|
||||
sizeClasses="min-w-[200px] !max-w-[400px] [--anchor-max-width:400px]"
|
||||
anchor="bottom start"
|
||||
position="left"
|
||||
testId="VoiceDropdown"
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue