mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +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
|
|
@ -31,7 +31,7 @@
|
||||||
"@ariakit/react": "^0.4.5",
|
"@ariakit/react": "^0.4.5",
|
||||||
"@dicebear/collection": "^7.0.4",
|
"@dicebear/collection": "^7.0.4",
|
||||||
"@dicebear/core": "^7.0.4",
|
"@dicebear/core": "^7.0.4",
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "^2.1.2",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||||
"@radix-ui/react-checkbox": "^1.0.3",
|
"@radix-ui/react-checkbox": "^1.0.3",
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ export default function PopoverButtons({
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
button.buttonClass,
|
button.buttonClass,
|
||||||
'border-2 border-gray-300/50 focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:focus:ring-green-500',
|
'border border-gray-300/50 focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:focus:ring-green-500',
|
||||||
'ml-1 h-full bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white',
|
'ml-1 h-full bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white',
|
||||||
buttonClass ?? '',
|
buttonClass ?? '',
|
||||||
)}
|
)}
|
||||||
|
|
@ -141,7 +141,7 @@ export default function PopoverButtons({
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
button.buttonClass,
|
button.buttonClass,
|
||||||
'flex justify-center border-2 border-gray-300/50 focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:focus:ring-green-500',
|
'flex justify-center border border-gray-300/50 focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:focus:ring-green-500',
|
||||||
'h-full w-full bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white',
|
'h-full w-full bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white',
|
||||||
buttonClass ?? '',
|
buttonClass ?? '',
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ const SetKeyDialog = ({
|
||||||
value={expiresAtLabel}
|
value={expiresAtLabel}
|
||||||
onChange={handleExpirationChange}
|
onChange={handleExpirationChange}
|
||||||
options={expirationOptions.map((option) => option.display)}
|
options={expirationOptions.map((option) => option.display)}
|
||||||
width={185}
|
sizeClasses="w-[185px]"
|
||||||
/>
|
/>
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<EndpointComponent
|
<EndpointComponent
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Disclosure } from '@headlessui/react';
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
|
||||||
import { useCallback, memo, ReactNode } from 'react';
|
import { useCallback, memo, ReactNode } from 'react';
|
||||||
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||||
import type { TResPlugin, TInput } from 'librechat-data-provider';
|
import type { TResPlugin, TInput } from 'librechat-data-provider';
|
||||||
|
|
@ -98,12 +98,12 @@ const Plugin: React.FC<PluginProps> = ({ plugin }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{plugin.loading && <Spinner className="ml-1 text-black" />}
|
{plugin.loading && <Spinner className="ml-1 text-black" />}
|
||||||
<Disclosure.Button className="ml-12 flex items-center gap-2">
|
<DisclosureButton className="ml-12 flex items-center gap-2">
|
||||||
<ChevronDownIcon {...iconProps} />
|
<ChevronDownIcon {...iconProps} />
|
||||||
</Disclosure.Button>
|
</DisclosureButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Disclosure.Panel className="mt-3 flex max-w-full flex-col gap-3">
|
<DisclosurePanel className="mt-3 flex max-w-full flex-col gap-3">
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
lang={latestPlugin ? `REQUEST TO ${latestPlugin?.toUpperCase()}` : 'REQUEST'}
|
lang={latestPlugin ? `REQUEST TO ${latestPlugin?.toUpperCase()}` : 'REQUEST'}
|
||||||
codeChildren={formatInputs(plugin.inputs ?? [])}
|
codeChildren={formatInputs(plugin.inputs ?? [])}
|
||||||
|
|
@ -120,7 +120,7 @@ const Plugin: React.FC<PluginProps> = ({ plugin }) => {
|
||||||
classProp="max-h-[450px]"
|
classProp="max-h-[450px]"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Disclosure.Panel>
|
</DisclosurePanel>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { FileText } from 'lucide-react';
|
import { FileText } from 'lucide-react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { Fragment, useState, memo } from 'react';
|
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 { useGetUserBalance, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||||
import FilesView from '~/components/Chat/Input/Files/FilesView';
|
import FilesView from '~/components/Chat/Input/Files/FilesView';
|
||||||
import { useAuthContext } from '~/hooks/AuthContext';
|
import { useAuthContext } from '~/hooks/AuthContext';
|
||||||
|
|
@ -32,7 +32,7 @@ function NavLinks() {
|
||||||
<Menu as="div" className="group relative">
|
<Menu as="div" className="group relative">
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<Menu.Button
|
<MenuButton
|
||||||
className={cn(
|
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',
|
'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' : '',
|
open ? 'bg-gray-100 dark:bg-gray-800' : '',
|
||||||
|
|
@ -64,7 +64,7 @@ function NavLinks() {
|
||||||
>
|
>
|
||||||
{user?.name || user?.username || localize('com_nav_user')}
|
{user?.name || user?.username || localize('com_nav_user')}
|
||||||
</div>
|
</div>
|
||||||
</Menu.Button>
|
</MenuButton>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
|
|
@ -75,7 +75,7 @@ function NavLinks() {
|
||||||
leaveFrom="translate-y-0 opacity-100"
|
leaveFrom="translate-y-0 opacity-100"
|
||||||
leaveTo="translate-y-2 opacity-0"
|
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">
|
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm" role="none">
|
||||||
{user?.email || localize('com_nav_user')}
|
{user?.email || localize('com_nav_user')}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -90,34 +90,34 @@ function NavLinks() {
|
||||||
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Menu.Item as="div">
|
<MenuItem as="div">
|
||||||
<NavLink
|
<NavLink
|
||||||
svg={() => <FileText className="icon-md" />}
|
svg={() => <FileText className="icon-md" />}
|
||||||
text={localize('com_nav_my_files')}
|
text={localize('com_nav_my_files')}
|
||||||
clickHandler={() => setShowFiles(true)}
|
clickHandler={() => setShowFiles(true)}
|
||||||
/>
|
/>
|
||||||
</Menu.Item>
|
</MenuItem>
|
||||||
{startupConfig?.helpAndFaqURL !== '/' && (
|
{startupConfig?.helpAndFaqURL !== '/' && (
|
||||||
<Menu.Item as="div">
|
<MenuItem as="div">
|
||||||
<NavLink
|
<NavLink
|
||||||
svg={() => <LinkIcon />}
|
svg={() => <LinkIcon />}
|
||||||
text={localize('com_nav_help_faq')}
|
text={localize('com_nav_help_faq')}
|
||||||
clickHandler={() => window.open(startupConfig?.helpAndFaqURL, '_blank')}
|
clickHandler={() => window.open(startupConfig?.helpAndFaqURL, '_blank')}
|
||||||
/>
|
/>
|
||||||
</Menu.Item>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<Menu.Item as="div">
|
<MenuItem as="div">
|
||||||
<NavLink
|
<NavLink
|
||||||
svg={() => <GearIcon className="icon-md" />}
|
svg={() => <GearIcon className="icon-md" />}
|
||||||
text={localize('com_nav_settings')}
|
text={localize('com_nav_settings')}
|
||||||
clickHandler={() => setShowSettings(true)}
|
clickHandler={() => setShowSettings(true)}
|
||||||
/>
|
/>
|
||||||
</Menu.Item>
|
</MenuItem>
|
||||||
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
||||||
<Menu.Item as="div">
|
<MenuItem as="div">
|
||||||
<Logout />
|
<Logout />
|
||||||
</Menu.Item>
|
</MenuItem>
|
||||||
</Menu.Items>
|
</MenuItems>
|
||||||
</Transition>
|
</Transition>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,14 @@ import * as Tabs from '@radix-ui/react-tabs';
|
||||||
import { MessageSquare } from 'lucide-react';
|
import { MessageSquare } from 'lucide-react';
|
||||||
import { SettingsTabValues } from 'librechat-data-provider';
|
import { SettingsTabValues } from 'librechat-data-provider';
|
||||||
import type { TDialogProps } from '~/common';
|
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 { GearIcon, DataIcon, SpeechIcon, UserIcon, ExperimentIcon } from '~/components/svg';
|
||||||
import { General, Messages, Speech, Beta, Data, Account } from './SettingsTabs';
|
import { General, Messages, Speech, Beta, Data, Account } from './SettingsTabs';
|
||||||
import { useMediaQuery, useLocalize } from '~/hooks';
|
import { useMediaQuery, useLocalize } from '~/hooks';
|
||||||
|
|
@ -13,132 +20,183 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Transition appear show={open}>
|
||||||
<DialogContent
|
<Dialog as="div" className="relative z-50 focus:outline-none" onClose={onOpenChange}>
|
||||||
disableScroll={isSmallScreen}
|
<TransitionChild
|
||||||
className={cn(
|
enter="ease-out duration-200"
|
||||||
'max-h-[90vh] overflow-auto shadow-2xl md:min-h-[500px] md:w-[680px]',
|
enterFrom="opacity-0"
|
||||||
isSmallScreen ? 'min-h-[200px]' : '',
|
enterTo="opacity-100"
|
||||||
)}
|
leave="ease-in duration-100"
|
||||||
>
|
leaveFrom="opacity-100"
|
||||||
<DialogHeader>
|
leaveTo="opacity-0"
|
||||||
<DialogTitle className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
|
>
|
||||||
{localize('com_nav_settings')}
|
<div className="fixed inset-0 bg-black/50 dark:bg-black/80" aria-hidden="true" />
|
||||||
</DialogTitle>
|
</TransitionChild>
|
||||||
</DialogHeader>
|
|
||||||
<div className="max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]">
|
<TransitionChild
|
||||||
<Tabs.Root
|
enter="ease-out duration-200"
|
||||||
defaultValue={SettingsTabValues.GENERAL}
|
enterFrom="opacity-0 scale-95"
|
||||||
className="flex flex-col gap-3 md:flex-row"
|
enterTo="opacity-100 scale-100"
|
||||||
orientation="horizontal"
|
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
|
<DialogPanel
|
||||||
aria-label="Settings"
|
|
||||||
role="tablist"
|
|
||||||
aria-orientation="horizontal"
|
|
||||||
className={cn(
|
className={cn(
|
||||||
isSmallScreen
|
'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]',
|
||||||
? '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',
|
|
||||||
)}
|
)}
|
||||||
style={{ outline: 'none' }}
|
|
||||||
>
|
>
|
||||||
<Tabs.Trigger
|
<DialogTitle
|
||||||
className={cn(
|
className="mb-3 flex items-center justify-between border-b border-black/10 p-6 pb-5 text-left dark:border-white/10"
|
||||||
'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',
|
as="div"
|
||||||
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' }}
|
|
||||||
>
|
>
|
||||||
<GearIcon />
|
<h2 className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
|
||||||
{localize('com_nav_setting_general')}
|
{localize('com_nav_settings')}
|
||||||
</Tabs.Trigger>
|
</h2>
|
||||||
<Tabs.Trigger
|
<button
|
||||||
className={cn(
|
type="button"
|
||||||
'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',
|
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"
|
||||||
isSmallScreen
|
onClick={() => onOpenChange(false)}
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
>
|
||||||
: 'bg-white radix-state-active:bg-gray-100',
|
<svg
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
)}
|
width="24"
|
||||||
value={SettingsTabValues.MESSAGES}
|
height="24"
|
||||||
style={{ userSelect: 'none' }}
|
viewBox="0 0 24 24"
|
||||||
>
|
fill="none"
|
||||||
<MessageSquare className="icon-sm" />
|
stroke="currentColor"
|
||||||
{localize('com_endpoint_messages')}
|
strokeWidth="2"
|
||||||
</Tabs.Trigger>
|
strokeLinecap="round"
|
||||||
<Tabs.Trigger
|
strokeLinejoin="round"
|
||||||
className={cn(
|
className="h-5 w-5 text-black dark:text-white"
|
||||||
'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
|
<line x1="18" x2="6" y1="6" y2="18"></line>
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
<line x1="6" x2="18" y1="6" y2="18"></line>
|
||||||
: 'bg-white radix-state-active:bg-gray-100',
|
</svg>
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
<span className="sr-only">Close</span>
|
||||||
)}
|
</button>
|
||||||
value={SettingsTabValues.BETA}
|
</DialogTitle>
|
||||||
style={{ userSelect: 'none' }}
|
<div className="max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]">
|
||||||
>
|
<Tabs.Root
|
||||||
<ExperimentIcon />
|
defaultValue={SettingsTabValues.GENERAL}
|
||||||
{localize('com_nav_setting_beta')}
|
className="flex flex-col gap-10 md:flex-row"
|
||||||
</Tabs.Trigger>
|
orientation="horizontal"
|
||||||
<Tabs.Trigger
|
>
|
||||||
className={cn(
|
<Tabs.List
|
||||||
'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',
|
aria-label="Settings"
|
||||||
isSmallScreen
|
role="tablist"
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
aria-orientation="horizontal"
|
||||||
: 'bg-white radix-state-active:bg-gray-100',
|
className={cn(
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
'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' : '',
|
||||||
value={SettingsTabValues.SPEECH}
|
)}
|
||||||
style={{ userSelect: 'none' }}
|
style={{ outline: 'none' }}
|
||||||
>
|
>
|
||||||
<SpeechIcon className="icon-sm" />
|
<Tabs.Trigger
|
||||||
{localize('com_nav_setting_speech')}
|
className={cn(
|
||||||
</Tabs.Trigger>
|
'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',
|
||||||
<Tabs.Trigger
|
isSmallScreen
|
||||||
className={cn(
|
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||||
'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',
|
: 'bg-white radix-state-active:bg-gray-200',
|
||||||
isSmallScreen
|
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
)}
|
||||||
: 'bg-white radix-state-active:bg-gray-100',
|
value={SettingsTabValues.GENERAL}
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
style={{ userSelect: 'none' }}
|
||||||
)}
|
>
|
||||||
value={SettingsTabValues.DATA}
|
<GearIcon />
|
||||||
style={{ userSelect: 'none' }}
|
{localize('com_nav_setting_general')}
|
||||||
>
|
</Tabs.Trigger>
|
||||||
<DataIcon />
|
<Tabs.Trigger
|
||||||
{localize('com_nav_setting_data')}
|
className={cn(
|
||||||
</Tabs.Trigger>
|
'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',
|
||||||
<Tabs.Trigger
|
isSmallScreen
|
||||||
className={cn(
|
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||||
'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',
|
: 'bg-white radix-state-active:bg-gray-200',
|
||||||
isSmallScreen
|
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
)}
|
||||||
: 'bg-white radix-state-active:bg-gray-100',
|
value={SettingsTabValues.MESSAGES}
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
style={{ userSelect: 'none' }}
|
||||||
)}
|
>
|
||||||
value={SettingsTabValues.ACCOUNT}
|
<MessageSquare className="icon-sm" />
|
||||||
style={{ userSelect: 'none' }}
|
{localize('com_endpoint_messages')}
|
||||||
>
|
</Tabs.Trigger>
|
||||||
<UserIcon />
|
<Tabs.Trigger
|
||||||
{localize('com_nav_setting_account')}
|
className={cn(
|
||||||
</Tabs.Trigger>
|
'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',
|
||||||
</Tabs.List>
|
isSmallScreen
|
||||||
<div className="h-auto min-h-[280px] overflow-auto sm:w-full sm:max-w-none">
|
? 'flex-1 items-center justify-center text-nowrap text-sm dark:text-gray-500 dark:radix-state-active:text-white'
|
||||||
<General />
|
: 'bg-white radix-state-active:bg-gray-200',
|
||||||
<Messages />
|
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||||
<Beta />
|
)}
|
||||||
<Speech />
|
value={SettingsTabValues.BETA}
|
||||||
<Data />
|
style={{ userSelect: 'none' }}
|
||||||
<Account />
|
>
|
||||||
</div>
|
<ExperimentIcon />
|
||||||
</Tabs.Root>
|
{localize('com_nav_setting_beta')}
|
||||||
</div>
|
</Tabs.Trigger>
|
||||||
</DialogContent>
|
<Tabs.Trigger
|
||||||
</Dialog>
|
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}
|
value={theme}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={themeOptions}
|
options={themeOptions}
|
||||||
width={180}
|
sizeClasses="w-[220px]"
|
||||||
position={'left'}
|
anchor="bottom start"
|
||||||
maxHeight="200px"
|
|
||||||
testId="theme-selector"
|
testId="theme-selector"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -111,8 +110,8 @@ export const LangSelector = ({
|
||||||
<Dropdown
|
<Dropdown
|
||||||
value={langcode}
|
value={langcode}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
position={'left'}
|
sizeClasses="[--anchor-max-height:256px]"
|
||||||
maxHeight="271px"
|
anchor="bottom start"
|
||||||
options={languageOptions}
|
options={languageOptions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ describe('LangSelector', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
|
global.ResizeObserver = class MockedResizeObserver {
|
||||||
|
observe = jest.fn();
|
||||||
|
unobserve = jest.fn();
|
||||||
|
disconnect = jest.fn();
|
||||||
|
};
|
||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<LangSelector langcode="en-US" onChange={mockOnChange} />
|
<LangSelector langcode="en-US" onChange={mockOnChange} />
|
||||||
|
|
@ -24,6 +29,11 @@ describe('LangSelector', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onChange when the select value changes', async () => {
|
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(
|
const { getByText, getByTestId } = render(
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<LangSelector langcode="en-US" onChange={mockOnChange} />
|
<LangSelector langcode="en-US" onChange={mockOnChange} />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
// ThemeSelector.spec.tsx
|
||||||
import 'test/matchMedia.mock';
|
import 'test/matchMedia.mock';
|
||||||
|
|
||||||
import React from 'react';
|
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 '@testing-library/jest-dom/extend-expect';
|
||||||
import { ThemeSelector } from './General';
|
import { ThemeSelector } from './General';
|
||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
|
|
@ -13,6 +15,11 @@ describe('ThemeSelector', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
|
global.ResizeObserver = class MockedResizeObserver {
|
||||||
|
observe = jest.fn();
|
||||||
|
unobserve = jest.fn();
|
||||||
|
disconnect = jest.fn();
|
||||||
|
};
|
||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<ThemeSelector theme="system" onChange={mockOnChange} />
|
<ThemeSelector theme="system" onChange={mockOnChange} />
|
||||||
|
|
@ -24,6 +31,11 @@ describe('ThemeSelector', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onChange when the select value changes', async () => {
|
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(
|
const { getByText, getByTestId } = render(
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<ThemeSelector theme="system" onChange={mockOnChange} />
|
<ThemeSelector theme="system" onChange={mockOnChange} />
|
||||||
|
|
@ -42,9 +54,9 @@ describe('ThemeSelector', () => {
|
||||||
const darkOption = getByText('Dark');
|
const darkOption = getByText('Dark');
|
||||||
fireEvent.click(darkOption);
|
fireEvent.click(darkOption);
|
||||||
|
|
||||||
// Ensure that the onChange is called with the expected value after a short delay
|
// Ensure that the onChange is called with the expected value
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await waitFor(() => {
|
||||||
|
expect(mockOnChange).toHaveBeenCalledWith('dark');
|
||||||
expect(mockOnChange).toHaveBeenCalledWith('dark');
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ export const ForkSettings = () => {
|
||||||
value={forkSetting}
|
value={forkSetting}
|
||||||
onChange={setForkSetting}
|
onChange={setForkSetting}
|
||||||
options={forkOptions}
|
options={forkOptions}
|
||||||
position={'left'}
|
sizeClasses="w-[200px]"
|
||||||
maxHeight="199px"
|
anchor="bottom start"
|
||||||
testId="fork-setting-dropdown"
|
testId="fork-setting-dropdown"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ export default function EngineSTTDropdown() {
|
||||||
value={engineSTT}
|
value={engineSTT}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
options={endpointOptions}
|
options={endpointOptions}
|
||||||
width={180}
|
sizeClasses="w-[180px]"
|
||||||
position={'left'}
|
anchor="bottom start"
|
||||||
testId="EngineSTTDropdown"
|
testId="EngineSTTDropdown"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -98,8 +98,8 @@ export default function LanguageSTTDropdown() {
|
||||||
value={languageSTT}
|
value={languageSTT}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
options={languageOptions}
|
options={languageOptions}
|
||||||
width={220}
|
sizeClasses="[--anchor-max-height:256px]"
|
||||||
position={'left'}
|
anchor="bottom start"
|
||||||
testId="LanguageSTTDropdown"
|
testId="LanguageSTTDropdown"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ function Speech() {
|
||||||
<Tabs.Content
|
<Tabs.Content
|
||||||
value={SettingsTabValues.SPEECH}
|
value={SettingsTabValues.SPEECH}
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
className="w-full px-4 md:min-h-[300px]"
|
className="w-full md:min-h-[271px]"
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
>
|
>
|
||||||
<Tabs.Root
|
<Tabs.Root
|
||||||
|
|
@ -138,8 +138,8 @@ function Speech() {
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
value={advancedMode ? 'advanced' : 'simple'}
|
value={advancedMode ? 'advanced' : 'simple'}
|
||||||
>
|
>
|
||||||
<div className="sticky top-0 z-50 bg-white dark:bg-gray-700">
|
<div className="sticky -top-1 z-50 mb-4 bg-white dark:bg-gray-700">
|
||||||
<Tabs.List className="sticky top-0 mb-4 flex justify-center bg-white dark:bg-gray-700">
|
<Tabs.List className="flex justify-center bg-white dark:bg-gray-700">
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
onClick={() => setAdvancedMode(false)}
|
onClick={() => setAdvancedMode(false)}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ export default function EngineTTSDropdown() {
|
||||||
value={engineTTS}
|
value={engineTTS}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
options={endpointOptions}
|
options={endpointOptions}
|
||||||
width={180}
|
sizeClasses="w-[180px]"
|
||||||
position={'left'}
|
anchor="bottom start"
|
||||||
testId="EngineTTSDropdown"
|
testId="EngineTTSDropdown"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ export default function VoiceDropdown() {
|
||||||
value={voice}
|
value={voice}
|
||||||
onChange={setVoice}
|
onChange={setVoice}
|
||||||
options={voiceOptions}
|
options={voiceOptions}
|
||||||
|
sizeClasses="min-w-[200px] !max-w-[400px] [--anchor-max-width:400px]"
|
||||||
|
anchor="bottom start"
|
||||||
position="left"
|
position="left"
|
||||||
testId="VoiceDropdown"
|
testId="VoiceDropdown"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Search, X } from 'lucide-react';
|
import { Search, X } from 'lucide-react';
|
||||||
import { Dialog } from '@headlessui/react';
|
import { Dialog, DialogPanel, DialogTitle } from '@headlessui/react';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
|
import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
|
||||||
import type { TError, TPlugin, TPluginAction } from 'librechat-data-provider';
|
import type { TError, TPlugin, TPluginAction } from 'librechat-data-provider';
|
||||||
|
|
@ -134,16 +134,16 @@ function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
|
||||||
<div className="fixed inset-0 bg-gray-600/65 transition-opacity dark:bg-black/80" />
|
<div className="fixed inset-0 bg-gray-600/65 transition-opacity dark:bg-black/80" />
|
||||||
{/* Full-screen container to center the panel */}
|
{/* Full-screen container to center the panel */}
|
||||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||||
<Dialog.Panel
|
<DialogPanel
|
||||||
className="relative w-full transform overflow-hidden overflow-y-auto rounded-lg bg-white text-left shadow-xl transition-all dark:bg-gray-700 max-sm:h-full sm:mx-7 sm:my-8 sm:max-w-2xl lg:max-w-5xl xl:max-w-7xl"
|
className="relative w-full transform overflow-hidden overflow-y-auto rounded-lg bg-white text-left shadow-xl transition-all dark:bg-gray-700 max-sm:h-full sm:mx-7 sm:my-8 sm:max-w-2xl lg:max-w-5xl xl:max-w-7xl"
|
||||||
style={{ minHeight: '610px' }}
|
style={{ minHeight: '610px' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between border-b-[1px] border-black/10 p-6 pb-4 dark:border-white/10">
|
<div className="flex items-center justify-between border-b-[1px] border-black/10 p-6 pb-4 dark:border-white/10">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="text-center sm:text-left">
|
<div className="text-center sm:text-left">
|
||||||
<Dialog.Title className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
|
<DialogTitle className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
|
||||||
{localize('com_nav_plugin_store')}
|
{localize('com_nav_plugin_store')}
|
||||||
</Dialog.Title>
|
</DialogTitle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -236,7 +236,7 @@ function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
|
||||||
</div> */}
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</DialogPanel>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Search, X } from 'lucide-react';
|
import { Search, X } from 'lucide-react';
|
||||||
import { Dialog } from '@headlessui/react';
|
import { Dialog, DialogPanel, DialogTitle, Description } from '@headlessui/react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||||
import type { AssistantsEndpoint, TError, TPluginAction } from 'librechat-data-provider';
|
import type { AssistantsEndpoint, TError, TPluginAction } from 'librechat-data-provider';
|
||||||
|
|
@ -142,19 +142,19 @@ function ToolSelectDialog({
|
||||||
<div className="fixed inset-0 bg-gray-600/65 transition-opacity dark:bg-black/80" />
|
<div className="fixed inset-0 bg-gray-600/65 transition-opacity dark:bg-black/80" />
|
||||||
{/* Full-screen container to center the panel */}
|
{/* Full-screen container to center the panel */}
|
||||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||||
<Dialog.Panel
|
<DialogPanel
|
||||||
className="relative w-full transform overflow-hidden overflow-y-auto rounded-lg bg-white text-left shadow-xl transition-all dark:bg-gray-800 max-sm:h-full sm:mx-7 sm:my-8 sm:max-w-2xl lg:max-w-5xl xl:max-w-7xl"
|
className="relative w-full transform overflow-hidden overflow-y-auto rounded-lg bg-white text-left shadow-xl transition-all dark:bg-gray-800 max-sm:h-full sm:mx-7 sm:my-8 sm:max-w-2xl lg:max-w-5xl xl:max-w-7xl"
|
||||||
style={{ minHeight: '610px' }}
|
style={{ minHeight: '610px' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between border-b-[1px] border-black/10 px-4 pb-4 pt-5 dark:border-white/10 sm:p-6">
|
<div className="flex items-center justify-between border-b-[1px] border-black/10 px-4 pb-4 pt-5 dark:border-white/10 sm:p-6">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="text-center sm:text-left">
|
<div className="text-center sm:text-left">
|
||||||
<Dialog.Title className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
|
<DialogTitle className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
|
||||||
{localize('com_nav_tool_dialog')}
|
{localize('com_nav_tool_dialog')}
|
||||||
</Dialog.Title>
|
</DialogTitle>
|
||||||
<Dialog.Description className="text-sm text-gray-500 dark:text-gray-300">
|
<Description className="text-sm text-gray-500 dark:text-gray-300">
|
||||||
{localize('com_nav_tool_dialog_description')}
|
{localize('com_nav_tool_dialog_description')}
|
||||||
</Dialog.Description>
|
</Description>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -232,7 +232,7 @@ function ToolSelectDialog({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</DialogPanel>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import React, { FC, useState } from 'react';
|
import React, { FC, useContext, useState } from 'react';
|
||||||
import { Listbox } from '@headlessui/react';
|
import {
|
||||||
|
Listbox,
|
||||||
|
ListboxButton,
|
||||||
|
ListboxOption,
|
||||||
|
ListboxOptions,
|
||||||
|
Transition,
|
||||||
|
} from '@headlessui/react';
|
||||||
|
import { AnchorPropsWithSelection } from '@headlessui/react/dist/internal/floating';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
type OptionType = {
|
type OptionType = {
|
||||||
|
|
@ -7,17 +14,14 @@ type OptionType = {
|
||||||
display?: string;
|
display?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DropdownPosition = 'left' | 'right';
|
|
||||||
|
|
||||||
interface DropdownProps {
|
interface DropdownProps {
|
||||||
value: string;
|
value: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
options: (string | OptionType)[];
|
options: (string | OptionType)[];
|
||||||
className?: string;
|
className?: string;
|
||||||
position?: DropdownPosition;
|
anchor?: AnchorPropsWithSelection;
|
||||||
width?: number;
|
sizeClasses?: string;
|
||||||
maxHeight?: string;
|
|
||||||
testId?: string;
|
testId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,18 +31,12 @@ const Dropdown: FC<DropdownProps> = ({
|
||||||
onChange,
|
onChange,
|
||||||
options,
|
options,
|
||||||
className = '',
|
className = '',
|
||||||
position = 'right',
|
anchor,
|
||||||
width,
|
sizeClasses,
|
||||||
maxHeight = 'auto',
|
|
||||||
testId = 'dropdown-menu',
|
testId = 'dropdown-menu',
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedValue, setSelectedValue] = useState(initialValue);
|
const [selectedValue, setSelectedValue] = useState(initialValue);
|
||||||
|
|
||||||
const positionClasses = {
|
|
||||||
right: 'origin-bottom-left left-0',
|
|
||||||
left: 'origin-bottom-right right-0',
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative', className)}>
|
<div className={cn('relative', className)}>
|
||||||
<Listbox
|
<Listbox
|
||||||
|
|
@ -49,7 +47,7 @@ const Dropdown: FC<DropdownProps> = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={cn('relative', className)}>
|
<div className={cn('relative', className)}>
|
||||||
<Listbox.Button
|
<ListboxButton
|
||||||
data-testid={testId}
|
data-testid={testId}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative inline-flex items-center justify-between rounded-md border-gray-50 bg-white py-2 pl-3 pr-8 text-black transition-all duration-100 ease-in-out hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:ring-white dark:focus:ring-offset-gray-700',
|
'relative inline-flex items-center justify-between rounded-md border-gray-50 bg-white py-2 pl-3 pr-8 text-black transition-all duration-100 ease-in-out hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:ring-white dark:focus:ring-offset-gray-700',
|
||||||
|
|
@ -76,52 +74,59 @@ const Dropdown: FC<DropdownProps> = ({
|
||||||
<polyline points="6 9 12 15 18 9"></polyline>
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Button>
|
</ListboxButton>
|
||||||
<Listbox.Options
|
<Transition
|
||||||
className={cn(
|
leave="transition ease-in duration-50"
|
||||||
`absolute z-50 mt-1 flex max-h-[40vh] flex-col items-start gap-1 overflow-auto rounded-lg border border-gray-100 bg-white p-1.5 text-black shadow-lg transition-opacity dark:border-gray-600 dark:bg-gray-700 dark:text-white ${positionClasses[position]}`,
|
leaveFrom="opacity-100"
|
||||||
className,
|
leaveTo="opacity-0"
|
||||||
)}
|
|
||||||
style={{ width: width ? `${width}px` : 'auto', maxHeight: maxHeight }}
|
|
||||||
aria-label="List of options"
|
|
||||||
>
|
>
|
||||||
{options.map((item, index) => (
|
<ListboxOptions
|
||||||
<Listbox.Option
|
className={cn(
|
||||||
key={index}
|
'absolute z-50 mt-1 flex flex-col items-start gap-1 overflow-auto rounded-lg border border-gray-300 bg-white bg-white p-1.5 text-gray-700 shadow-lg transition-opacity focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white',
|
||||||
value={typeof item === 'string' ? item : item.value}
|
sizeClasses,
|
||||||
className={cn(
|
className,
|
||||||
'duration-50 relative cursor-pointer select-none rounded border-gray-50 bg-white py-2.5 pl-3 pr-2 text-black transition-all ease-in-out hover:bg-gray-100 focus:bg-gray-200 dark:border-gray-50 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:bg-gray-500',
|
)}
|
||||||
)}
|
anchor={anchor}
|
||||||
style={{ width: '100%' }}
|
aria-label="List of options"
|
||||||
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
>
|
||||||
>
|
{options.map((item, index) => (
|
||||||
<div className="flex w-full items-center justify-between">
|
<ListboxOption
|
||||||
<span className="block truncate">
|
key={index}
|
||||||
{typeof item === 'string' ? item : (item as OptionType).display}
|
value={typeof item === 'string' ? item : item.value}
|
||||||
</span>
|
className={cn(
|
||||||
{selectedValue === (typeof item === 'string' ? item : item.value) && (
|
'relative cursor-pointer select-none rounded border-gray-300 bg-white py-2.5 pl-3 pr-3 text-sm text-gray-700 hover:bg-gray-100 dark:border-gray-300 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
|
||||||
<span className="ml-auto pl-2">
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
className="icon-md block group-hover:hidden"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
clipRule="evenodd"
|
|
||||||
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM16.0755 7.93219C16.5272 8.25003 16.6356 8.87383 16.3178 9.32549L11.5678 16.0755C11.3931 16.3237 11.1152 16.4792 10.8123 16.4981C10.5093 16.517 10.2142 16.3973 10.0101 16.1727L7.51006 13.4227C7.13855 13.014 7.16867 12.3816 7.57733 12.0101C7.98598 11.6386 8.61843 11.6687 8.98994 12.0773L10.6504 13.9039L14.6822 8.17451C15 7.72284 15.6238 7.61436 16.0755 7.93219Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
style={{ width: '100%' }}
|
||||||
</Listbox.Option>
|
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
||||||
))}
|
>
|
||||||
</Listbox.Options>
|
<div className="flex w-full items-center justify-between">
|
||||||
|
<span className="block truncate">
|
||||||
|
{typeof item === 'string' ? item : (item as OptionType).display}
|
||||||
|
</span>
|
||||||
|
{selectedValue === (typeof item === 'string' ? item : item.value) && (
|
||||||
|
<span className="ml-auto pl-2">
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="icon-md block group-hover:hidden"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM16.0755 7.93219C16.5272 8.25003 16.6356 8.87383 16.3178 9.32549L11.5678 16.0755C11.3931 16.3237 11.1152 16.4792 10.8123 16.4981C10.5093 16.517 10.2142 16.3973 10.0101 16.1727L7.51006 13.4227C7.13855 13.014 7.16867 12.3816 7.57733 12.0101C7.98598 11.6386 8.61843 11.6687 8.98994 12.0773L10.6504 13.9039L14.6822 8.17451C15 7.72284 15.6238 7.61436 16.0755 7.93219Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ListboxOption>
|
||||||
|
))}
|
||||||
|
</ListboxOptions>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC, useContext, useState } from 'react';
|
||||||
import { Listbox } from '@headlessui/react';
|
import {
|
||||||
|
Listbox,
|
||||||
|
ListboxButton,
|
||||||
|
ListboxOption,
|
||||||
|
ListboxOptions,
|
||||||
|
Transition,
|
||||||
|
} from '@headlessui/react';
|
||||||
|
import { AnchorPropsWithSelection } from '@headlessui/react/dist/internal/floating';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
type OptionType = {
|
type OptionType = {
|
||||||
|
|
@ -7,17 +14,14 @@ type OptionType = {
|
||||||
display?: string;
|
display?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DropdownPosition = 'left' | 'right';
|
|
||||||
|
|
||||||
interface DropdownProps {
|
interface DropdownProps {
|
||||||
value: string;
|
value: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
options: (string | OptionType)[];
|
options: (string | OptionType)[];
|
||||||
className?: string;
|
className?: string;
|
||||||
position?: DropdownPosition;
|
anchor?: AnchorPropsWithSelection;
|
||||||
width?: number;
|
sizeClasses?: string;
|
||||||
maxHeight?: string;
|
|
||||||
testId?: string;
|
testId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,16 +31,10 @@ const Dropdown: FC<DropdownProps> = ({
|
||||||
onChange,
|
onChange,
|
||||||
options,
|
options,
|
||||||
className = '',
|
className = '',
|
||||||
position = 'right',
|
anchor,
|
||||||
width,
|
sizeClasses,
|
||||||
maxHeight = 'auto',
|
|
||||||
testId = 'dropdown-menu',
|
testId = 'dropdown-menu',
|
||||||
}) => {
|
}) => {
|
||||||
const positionClasses = {
|
|
||||||
right: 'origin-bottom-left left-0',
|
|
||||||
left: 'origin-bottom-right right-0',
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative', className)}>
|
<div className={cn('relative', className)}>
|
||||||
<Listbox
|
<Listbox
|
||||||
|
|
@ -46,13 +44,14 @@ const Dropdown: FC<DropdownProps> = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={cn('relative', className)}>
|
<div className={cn('relative', className)}>
|
||||||
<Listbox.Button
|
<ListboxButton
|
||||||
data-testid={testId}
|
data-testid={testId}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative inline-flex items-center justify-between rounded-md border-gray-300 bg-white py-2 pl-3 pr-8 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
|
'relative inline-flex items-center justify-between rounded-md border-gray-50 bg-white py-2 pl-3 pr-8 text-black transition-all duration-100 ease-in-out hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:ring-white dark:focus:ring-offset-gray-700',
|
||||||
'w-auto',
|
'w-auto',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
aria-label="Select an option"
|
||||||
>
|
>
|
||||||
<span className="block truncate">
|
<span className="block truncate">
|
||||||
{label}
|
{label}
|
||||||
|
|
@ -67,35 +66,45 @@ const Dropdown: FC<DropdownProps> = ({
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="h-4 w-5 rotate-0 transform text-gray-400 transition-transform duration-300 ease-in-out"
|
className="h-4 w-5 rotate-0 transform text-black transition-transform duration-300 ease-in-out dark:text-gray-50"
|
||||||
>
|
>
|
||||||
<polyline points="6 9 12 15 18 9"></polyline>
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Button>
|
</ListboxButton>
|
||||||
<Listbox.Options
|
<Transition
|
||||||
className={cn(
|
leave="transition ease-in duration-50"
|
||||||
`absolute z-50 mt-1 flex max-h-[40vh] flex-col items-start gap-1 overflow-auto rounded-lg border border-gray-300 bg-white p-1.5 text-gray-700 shadow-lg transition-opacity focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white ${positionClasses[position]}`,
|
leaveFrom="opacity-100"
|
||||||
className,
|
leaveTo="opacity-0"
|
||||||
)}
|
|
||||||
style={{ width: width ? `${width}px` : 'auto', maxHeight: maxHeight }}
|
|
||||||
>
|
>
|
||||||
{options.map((item, index) => (
|
<ListboxOptions
|
||||||
<Listbox.Option
|
className={cn(
|
||||||
key={index}
|
'absolute z-50 mt-1 flex flex-col items-start gap-1 overflow-auto rounded-lg border border-gray-300 bg-white bg-white p-1.5 text-gray-700 shadow-lg transition-opacity focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white',
|
||||||
value={typeof item === 'string' ? item : item.value}
|
sizeClasses,
|
||||||
className={cn(
|
className,
|
||||||
'relative cursor-pointer select-none rounded border-gray-300 bg-white py-2.5 pl-3 pr-6 text-gray-700 hover:bg-gray-100 dark:border-gray-300 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
|
)}
|
||||||
)}
|
anchor={anchor}
|
||||||
style={{ width: '100%' }}
|
aria-label="List of options"
|
||||||
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
>
|
||||||
>
|
{options.map((item, index) => (
|
||||||
<span className="block truncate">
|
<ListboxOption
|
||||||
{typeof item === 'string' ? item : (item as OptionType).display}
|
key={index}
|
||||||
</span>
|
value={typeof item === 'string' ? item : item.value}
|
||||||
</Listbox.Option>
|
className={cn(
|
||||||
))}
|
'relative cursor-pointer select-none rounded border-gray-300 bg-white py-2.5 pl-3 pr-3 text-sm text-gray-700 hover:bg-gray-100 dark:border-gray-300 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600',
|
||||||
</Listbox.Options>
|
)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
||||||
|
>
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
|
<span className="block truncate">
|
||||||
|
{typeof item === 'string' ? item : (item as OptionType).display}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ListboxOption>
|
||||||
|
))}
|
||||||
|
</ListboxOptions>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import { Listbox, Transition } from '@headlessui/react';
|
import {
|
||||||
|
Listbox,
|
||||||
|
ListboxButton,
|
||||||
|
Label,
|
||||||
|
ListboxOptions,
|
||||||
|
ListboxOption,
|
||||||
|
Transition,
|
||||||
|
} from '@headlessui/react';
|
||||||
import { Wrench, ArrowRight } from 'lucide-react';
|
import { Wrench, ArrowRight } from 'lucide-react';
|
||||||
import { CheckMark } from '~/components/svg';
|
import { CheckMark } from '~/components/svg';
|
||||||
import useOnClickOutside from '~/hooks/useOnClickOutside';
|
import useOnClickOutside from '~/hooks/useOnClickOutside';
|
||||||
|
|
@ -74,7 +81,7 @@ function MultiSelectDropDown({
|
||||||
<Listbox value={value} onChange={handleSelect} disabled={disabled}>
|
<Listbox value={value} onChange={handleSelect} disabled={disabled}>
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<Listbox.Button
|
<ListboxButton
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-gray-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
|
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-gray-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
|
||||||
className ?? '',
|
className ?? '',
|
||||||
|
|
@ -85,13 +92,13 @@ function MultiSelectDropDown({
|
||||||
>
|
>
|
||||||
{' '}
|
{' '}
|
||||||
{showLabel && (
|
{showLabel && (
|
||||||
<Listbox.Label
|
<Label
|
||||||
className={cn('block text-xs text-gray-700 dark:text-gray-500', labelClassName)}
|
className={cn('block text-xs text-gray-700 dark:text-gray-500', labelClassName)}
|
||||||
id={excludeIds[1]}
|
id={excludeIds[1]}
|
||||||
data-headlessui-state=""
|
data-headlessui-state=""
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Listbox.Label>
|
</Label>
|
||||||
)}
|
)}
|
||||||
<span className="inline-flex w-full truncate" id={excludeIds[2]}>
|
<span className="inline-flex w-full truncate" id={excludeIds[2]}>
|
||||||
<span
|
<span
|
||||||
|
|
@ -144,7 +151,7 @@ function MultiSelectDropDown({
|
||||||
<polyline points="6 9 12 15 18 9"></polyline>
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Button>
|
</ListboxButton>
|
||||||
<Transition
|
<Transition
|
||||||
show={isOpen}
|
show={isOpen}
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
|
|
@ -153,7 +160,7 @@ function MultiSelectDropDown({
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
{...transitionProps}
|
{...transitionProps}
|
||||||
>
|
>
|
||||||
<Listbox.Options
|
<ListboxOptions
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute z-50 mt-2 max-h-60 w-full overflow-auto rounded bg-white text-base text-xs ring-1 ring-black/10 focus:outline-none dark:bg-gray-800 dark:ring-white/20 dark:last:border-0 md:w-[100%]',
|
'absolute z-50 mt-2 max-h-60 w-full overflow-auto rounded bg-white text-base text-xs ring-1 ring-black/10 focus:outline-none dark:bg-gray-800 dark:ring-white/20 dark:last:border-0 md:w-[100%]',
|
||||||
|
|
@ -167,7 +174,7 @@ function MultiSelectDropDown({
|
||||||
}
|
}
|
||||||
const selected = isSelected(option[optionValueKey]);
|
const selected = isSelected(option[optionValueKey]);
|
||||||
return (
|
return (
|
||||||
<Listbox.Option
|
<ListboxOption
|
||||||
key={i}
|
key={i}
|
||||||
value={option[optionValueKey]}
|
value={option[optionValueKey]}
|
||||||
className="group relative flex h-[42px] cursor-pointer select-none items-center overflow-hidden border-b border-black/10 pl-3 pr-9 text-gray-800 last:border-0 hover:bg-gray-20 dark:border-white/20 dark:text-white dark:hover:bg-gray-700"
|
className="group relative flex h-[42px] cursor-pointer select-none items-center overflow-hidden border-b border-black/10 pl-3 pr-9 text-gray-800 last:border-0 hover:bg-gray-20 dark:border-white/20 dark:text-white dark:hover:bg-gray-700"
|
||||||
|
|
@ -208,10 +215,10 @@ function MultiSelectDropDown({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Option>
|
</ListboxOption>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Listbox.Options>
|
</ListboxOptions>
|
||||||
</Transition>
|
</Transition>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Listbox, Transition } from '@headlessui/react';
|
import {
|
||||||
|
Listbox,
|
||||||
|
ListboxButton,
|
||||||
|
Label,
|
||||||
|
ListboxOptions,
|
||||||
|
ListboxOption,
|
||||||
|
Transition,
|
||||||
|
} from '@headlessui/react';
|
||||||
import type { Option, OptionWithIcon } from '~/common';
|
import type { Option, OptionWithIcon } from '~/common';
|
||||||
import CheckMark from '../svg/CheckMark';
|
import CheckMark from '../svg/CheckMark';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
@ -84,8 +91,7 @@ function SelectDropDown({
|
||||||
<Listbox value={value} onChange={setValue} disabled={disabled}>
|
<Listbox value={value} onChange={setValue} disabled={disabled}>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<Listbox.Button
|
<ListboxButton
|
||||||
tabIndex={tabIndex}
|
|
||||||
data-testid="select-dropdown-button"
|
data-testid="select-dropdown-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left dark:border-gray-600 dark:bg-gray-700 sm:text-sm',
|
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left dark:border-gray-600 dark:bg-gray-700 sm:text-sm',
|
||||||
|
|
@ -94,13 +100,13 @@ function SelectDropDown({
|
||||||
>
|
>
|
||||||
{' '}
|
{' '}
|
||||||
{showLabel && (
|
{showLabel && (
|
||||||
<Listbox.Label
|
<Label
|
||||||
className="block text-xs text-gray-700 dark:text-gray-500 "
|
className="block text-xs text-gray-700 dark:text-gray-500 "
|
||||||
id="headlessui-listbox-label-:r1:"
|
id="headlessui-listbox-label-:r1:"
|
||||||
data-headlessui-state=""
|
data-headlessui-state=""
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Listbox.Label>
|
</Label>
|
||||||
)}
|
)}
|
||||||
<span className="inline-flex w-full truncate">
|
<span className="inline-flex w-full truncate">
|
||||||
<span
|
<span
|
||||||
|
|
@ -138,7 +144,7 @@ function SelectDropDown({
|
||||||
<polyline points="6 9 12 15 18 9"></polyline>
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Button>
|
</ListboxButton>
|
||||||
<Transition
|
<Transition
|
||||||
show={open}
|
show={open}
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
|
|
@ -147,14 +153,14 @@ function SelectDropDown({
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
{...transitionProps}
|
{...transitionProps}
|
||||||
>
|
>
|
||||||
<Listbox.Options
|
<ListboxOptions
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute z-10 mt-2 max-h-60 w-full overflow-auto rounded border bg-white text-xs ring-black/10 dark:border-gray-600 dark:bg-gray-700 dark:ring-white/20 md:w-[100%]',
|
'absolute z-10 mt-2 max-h-60 w-full overflow-auto rounded border bg-white text-xs ring-black/10 dark:border-gray-600 dark:bg-gray-700 dark:ring-white/20 md:w-[100%]',
|
||||||
optionsListClass ?? '',
|
optionsListClass ?? '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{renderOption && (
|
{renderOption && (
|
||||||
<Listbox.Option
|
<ListboxOption
|
||||||
key={'listbox-render-option'}
|
key={'listbox-render-option'}
|
||||||
value={null}
|
value={null}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -163,7 +169,7 @@ function SelectDropDown({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{renderOption()}
|
{renderOption()}
|
||||||
</Listbox.Option>
|
</ListboxOption>
|
||||||
)}
|
)}
|
||||||
{searchRender}
|
{searchRender}
|
||||||
{options.map((option: string | Option, i: number) => {
|
{options.map((option: string | Option, i: number) => {
|
||||||
|
|
@ -181,7 +187,7 @@ function SelectDropDown({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Listbox.Option
|
<ListboxOption
|
||||||
key={i}
|
key={i}
|
||||||
value={currentValue}
|
value={currentValue}
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
|
|
@ -214,10 +220,10 @@ function SelectDropDown({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Option>
|
</ListboxOption>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Listbox.Options>
|
</ListboxOptions>
|
||||||
</Transition>
|
</Transition>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,7 @@ import '@testing-library/jest-dom/extend-expect';
|
||||||
// Mock canvas when run unit test cases with jest.
|
// Mock canvas when run unit test cases with jest.
|
||||||
// 'react-lottie' uses canvas
|
// 'react-lottie' uses canvas
|
||||||
import 'jest-canvas-mock';
|
import 'jest-canvas-mock';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
|
||||||
189
package-lock.json
generated
189
package-lock.json
generated
|
|
@ -1116,7 +1116,7 @@
|
||||||
"@ariakit/react": "^0.4.5",
|
"@ariakit/react": "^0.4.5",
|
||||||
"@dicebear/collection": "^7.0.4",
|
"@dicebear/collection": "^7.0.4",
|
||||||
"@dicebear/core": "^7.0.4",
|
"@dicebear/core": "^7.0.4",
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "^2.1.2",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||||
"@radix-ui/react-checkbox": "^1.0.3",
|
"@radix-ui/react-checkbox": "^1.0.3",
|
||||||
|
|
@ -5960,12 +5960,28 @@
|
||||||
"@floating-ui/utils": "^0.2.1"
|
"@floating-ui/utils": "^0.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/react-dom": {
|
"node_modules/@floating-ui/react": {
|
||||||
"version": "2.0.8",
|
"version": "0.26.19",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.19.tgz",
|
||||||
"integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==",
|
"integrity": "sha512-Jk6zITdjjIvjO/VdQFvpRaD3qPwOHH6AoDHxjhpy+oK4KFgaSP871HYWUAPdnLmx1gQ+w/pB312co3tVml+BXA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/dom": "^1.6.1"
|
"@floating-ui/react-dom": "^2.1.1",
|
||||||
|
"@floating-ui/utils": "^0.2.4",
|
||||||
|
"tabbable": "^6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/react-dom": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
|
|
@ -5973,9 +5989,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/utils": {
|
"node_modules/@floating-ui/utils": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz",
|
||||||
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
|
"integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@google/generative-ai": {
|
"node_modules/@google/generative-ai": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
|
|
@ -6015,19 +6032,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@headlessui/react": {
|
"node_modules/@headlessui/react": {
|
||||||
"version": "1.7.18",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.18.tgz",
|
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.1.2.tgz",
|
||||||
"integrity": "sha512-4i5DOrzwN4qSgNsL4Si61VMkUcWbcSKueUV7sFhpHzQcSShdlHENE5+QBntMSRvHt8NyoFO2AGG8si9lq+w4zQ==",
|
"integrity": "sha512-Kb3hgk9gRNRcTZktBrKdHhF3xFhYkca1Rk6e1/im2ENf83dgN54orMW0uSKTXFnUpZOUFZ+wcY05LlipwgZIFQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-virtual": "^3.0.0-beta.60",
|
"@floating-ui/react": "^0.26.16",
|
||||||
"client-only": "^0.0.1"
|
"@react-aria/focus": "^3.17.1",
|
||||||
|
"@react-aria/interactions": "^3.21.3",
|
||||||
|
"@tanstack/react-virtual": "^3.8.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16 || ^17 || ^18",
|
"react": "^18",
|
||||||
"react-dom": "^16 || ^17 || ^18"
|
"react-dom": "^18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
|
|
@ -8531,6 +8551,86 @@
|
||||||
"node": ">=8.x"
|
"node": ">=8.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-aria/focus": {
|
||||||
|
"version": "3.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.17.1.tgz",
|
||||||
|
"integrity": "sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-aria/interactions": "^3.21.3",
|
||||||
|
"@react-aria/utils": "^3.24.1",
|
||||||
|
"@react-types/shared": "^3.23.1",
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"clsx": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-aria/focus/node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-aria/interactions": {
|
||||||
|
"version": "3.21.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.21.3.tgz",
|
||||||
|
"integrity": "sha512-BWIuf4qCs5FreDJ9AguawLVS0lV9UU+sK4CCnbCNNmYqOWY+1+gRXCsnOM32K+oMESBxilAjdHW5n1hsMqYMpA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-aria/ssr": "^3.9.4",
|
||||||
|
"@react-aria/utils": "^3.24.1",
|
||||||
|
"@react-types/shared": "^3.23.1",
|
||||||
|
"@swc/helpers": "^0.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-aria/ssr": {
|
||||||
|
"version": "3.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.4.tgz",
|
||||||
|
"integrity": "sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-aria/utils": {
|
||||||
|
"version": "3.24.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.24.1.tgz",
|
||||||
|
"integrity": "sha512-O3s9qhPMd6n42x9sKeJ3lhu5V1Tlnzhu6Yk8QOvDuXf7UGuUjXf9mzfHJt1dYzID4l9Fwm8toczBzPM9t0jc8Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-aria/ssr": "^3.9.4",
|
||||||
|
"@react-stately/utils": "^3.10.1",
|
||||||
|
"@react-types/shared": "^3.23.1",
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"clsx": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-aria/utils/node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-dnd/asap": {
|
"node_modules/@react-dnd/asap": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
|
||||||
|
|
@ -8546,6 +8646,27 @@
|
||||||
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
|
||||||
"integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
|
"integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-stately/utils": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-types/shared": {
|
||||||
|
"version": "3.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.23.1.tgz",
|
||||||
|
"integrity": "sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@remix-run/router": {
|
"node_modules/@remix-run/router": {
|
||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz",
|
||||||
|
|
@ -9542,6 +9663,15 @@
|
||||||
"sourcemap-codec": "^1.4.8"
|
"sourcemap-codec": "^1.4.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@swc/helpers": {
|
||||||
|
"version": "0.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz",
|
||||||
|
"integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/match-sorter-utils": {
|
"node_modules/@tanstack/match-sorter-utils": {
|
||||||
"version": "8.11.8",
|
"version": "8.11.8",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.11.8.tgz",
|
||||||
|
|
@ -9633,11 +9763,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/react-virtual": {
|
"node_modules/@tanstack/react-virtual": {
|
||||||
"version": "3.0.4",
|
"version": "3.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.8.2.tgz",
|
||||||
"integrity": "sha512-tiqKW/e2MJVCr7/pRUXulpkyxllaOclkHNfhKTo4pmHjJIqnhMfwIjc1Q1R0Un3PI3kQywywu/791c8z9u0qeA==",
|
"integrity": "sha512-g78+DA29K0ByAfDkuibfLQqDshf8Aha/zcyEZ+huAX/yS/TWj/CUiEY4IJfDrFacdxIFmsLm0u4VtsLSKTngRw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/virtual-core": "3.0.0"
|
"@tanstack/virtual-core": "3.8.2"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -9661,9 +9792,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/virtual-core": {
|
"node_modules/@tanstack/virtual-core": {
|
||||||
"version": "3.0.0",
|
"version": "3.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.8.2.tgz",
|
||||||
"integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==",
|
"integrity": "sha512-ffpN6kTaPGwQPoWMcBAHbdv2ZCpj1SugldoYAcY0C4xH+Pej1KCOEUisNeEgbUnXOp8Y/4q6wGPu2tFHthOIQw==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
|
@ -12296,11 +12428,6 @@
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/client-only": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
|
||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
|
||||||
},
|
|
||||||
"node_modules/clipboardy": {
|
"node_modules/clipboardy": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz",
|
||||||
|
|
@ -26431,6 +26558,12 @@
|
||||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||||
"devOptional": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/tabbable": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tailwind-merge": {
|
"node_modules/tailwind-merge": {
|
||||||
"version": "1.14.0",
|
"version": "1.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue