mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 01:10: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,23 +20,72 @@ 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
|
||||||
|
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(
|
className={cn(
|
||||||
'max-h-[90vh] overflow-auto shadow-2xl md:min-h-[500px] md:w-[680px]',
|
'fixed inset-0 flex w-screen items-center justify-center p-4',
|
||||||
isSmallScreen ? 'min-h-[200px]' : '',
|
isSmallScreen ? '' : '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogPanel
|
||||||
<DialogTitle className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
|
className={cn(
|
||||||
|
'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]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<h2 className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
|
||||||
{localize('com_nav_settings')}
|
{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>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
|
||||||
<div className="max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]">
|
<div className="max-h-[373px] overflow-auto px-6 md:min-h-[373px] md:w-[680px]">
|
||||||
<Tabs.Root
|
<Tabs.Root
|
||||||
defaultValue={SettingsTabValues.GENERAL}
|
defaultValue={SettingsTabValues.GENERAL}
|
||||||
className="flex flex-col gap-3 md:flex-row"
|
className="flex flex-col gap-10 md:flex-row"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
>
|
>
|
||||||
<Tabs.List
|
<Tabs.List
|
||||||
|
|
@ -37,18 +93,17 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
role="tablist"
|
role="tablist"
|
||||||
aria-orientation="horizontal"
|
aria-orientation="horizontal"
|
||||||
className={cn(
|
className={cn(
|
||||||
isSmallScreen
|
'min-w-auto max-w-auto -ml-[8px] flex flex-shrink-0 flex-col flex-nowrap overflow-auto sm:max-w-none',
|
||||||
? 'hide-scrollbar flex flex-row space-x-4 overflow-x-auto'
|
isSmallScreen ? 'flex-row rounded-lg bg-gray-200 p-1 dark:bg-gray-800' : '',
|
||||||
: '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' }}
|
style={{ outline: 'none' }}
|
||||||
>
|
>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className={cn(
|
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',
|
'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
|
isSmallScreen
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
? '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-100',
|
: 'bg-white radix-state-active:bg-gray-200',
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||||
)}
|
)}
|
||||||
value={SettingsTabValues.GENERAL}
|
value={SettingsTabValues.GENERAL}
|
||||||
|
|
@ -59,10 +114,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className={cn(
|
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',
|
'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
|
isSmallScreen
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
? '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-100',
|
: 'bg-white radix-state-active:bg-gray-200',
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||||
)}
|
)}
|
||||||
value={SettingsTabValues.MESSAGES}
|
value={SettingsTabValues.MESSAGES}
|
||||||
|
|
@ -73,10 +128,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className={cn(
|
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',
|
'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
|
isSmallScreen
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
? '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-100',
|
: 'bg-white radix-state-active:bg-gray-200',
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||||
)}
|
)}
|
||||||
value={SettingsTabValues.BETA}
|
value={SettingsTabValues.BETA}
|
||||||
|
|
@ -87,10 +142,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className={cn(
|
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',
|
'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
|
isSmallScreen
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
? '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-100',
|
: 'bg-white radix-state-active:bg-gray-200',
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||||
)}
|
)}
|
||||||
value={SettingsTabValues.SPEECH}
|
value={SettingsTabValues.SPEECH}
|
||||||
|
|
@ -101,10 +156,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className={cn(
|
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',
|
'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
|
isSmallScreen
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
? '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-100',
|
: 'bg-white radix-state-active:bg-gray-200',
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||||
)}
|
)}
|
||||||
value={SettingsTabValues.DATA}
|
value={SettingsTabValues.DATA}
|
||||||
|
|
@ -115,10 +170,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
className={cn(
|
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',
|
'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
|
isSmallScreen
|
||||||
? 'flex-row items-center justify-center text-sm radix-state-active:bg-gray-100'
|
? '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-100',
|
: 'bg-white radix-state-active:bg-gray-200',
|
||||||
isSmallScreen ? '' : 'dark:bg-gray-700',
|
isSmallScreen ? '' : 'dark:bg-gray-700',
|
||||||
)}
|
)}
|
||||||
value={SettingsTabValues.ACCOUNT}
|
value={SettingsTabValues.ACCOUNT}
|
||||||
|
|
@ -128,7 +183,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
{localize('com_nav_setting_account')}
|
{localize('com_nav_setting_account')}
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<div className="h-auto min-h-[280px] overflow-auto sm:w-full sm:max-w-none">
|
<div className="max-h-[373px] overflow-auto sm:w-full sm:max-w-none md:pr-0.5 md:pt-0.5">
|
||||||
<General />
|
<General />
|
||||||
<Messages />
|
<Messages />
|
||||||
<Beta />
|
<Beta />
|
||||||
|
|
@ -138,7 +193,10 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
|
||||||
</div>
|
</div>
|
||||||
</Tabs.Root>
|
</Tabs.Root>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogPanel>
|
||||||
|
</div>
|
||||||
|
</TransitionChild>
|
||||||
</Dialog>
|
</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,21 +74,27 @@ 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
|
||||||
|
leave="transition ease-in duration-50"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<ListboxOptions
|
||||||
className={cn(
|
className={cn(
|
||||||
`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]}`,
|
'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',
|
||||||
|
sizeClasses,
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
style={{ width: width ? `${width}px` : 'auto', maxHeight: maxHeight }}
|
anchor={anchor}
|
||||||
aria-label="List of options"
|
aria-label="List of options"
|
||||||
>
|
>
|
||||||
{options.map((item, index) => (
|
{options.map((item, index) => (
|
||||||
<Listbox.Option
|
<ListboxOption
|
||||||
key={index}
|
key={index}
|
||||||
value={typeof item === 'string' ? item : item.value}
|
value={typeof item === 'string' ? item : item.value}
|
||||||
className={cn(
|
className={cn(
|
||||||
'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',
|
'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',
|
||||||
)}
|
)}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
||||||
|
|
@ -119,9 +123,10 @@ const Dropdown: FC<DropdownProps> = ({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Listbox.Option>
|
</ListboxOption>
|
||||||
))}
|
))}
|
||||||
</Listbox.Options>
|
</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
|
||||||
|
leave="transition ease-in duration-50"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<ListboxOptions
|
||||||
className={cn(
|
className={cn(
|
||||||
`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]}`,
|
'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',
|
||||||
|
sizeClasses,
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
style={{ width: width ? `${width}px` : 'auto', maxHeight: maxHeight }}
|
anchor={anchor}
|
||||||
|
aria-label="List of options"
|
||||||
>
|
>
|
||||||
{options.map((item, index) => (
|
{options.map((item, index) => (
|
||||||
<Listbox.Option
|
<ListboxOption
|
||||||
key={index}
|
key={index}
|
||||||
value={typeof item === 'string' ? item : item.value}
|
value={typeof item === 'string' ? item : item.value}
|
||||||
className={cn(
|
className={cn(
|
||||||
'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',
|
'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',
|
||||||
)}
|
)}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
data-theme={typeof item === 'string' ? item : (item as OptionType).value}
|
||||||
>
|
>
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
<span className="block truncate">
|
<span className="block truncate">
|
||||||
{typeof item === 'string' ? item : (item as OptionType).display}
|
{typeof item === 'string' ? item : (item as OptionType).display}
|
||||||
</span>
|
</span>
|
||||||
</Listbox.Option>
|
</div>
|
||||||
|
</ListboxOption>
|
||||||
))}
|
))}
|
||||||
</Listbox.Options>
|
</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