mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-21 10:50:14 +01:00
* feat: new dropdown * fix: maintain popover active when open * fix: update DeleteButton and ShareButton component to use useState for managing dialog state * BREAKING: style improvement of base Button component * style: update export button * a11y: ExportAndShareButton * add border * quick style fix * fix: flick issue on convo * fix: DropDown opens when renaming * chore: update radix-ui/react-dropdown-menu to latest * small fix * style: bookmarks update * reorder export modal * feat: imporved dropdowns * style: a lot of changes; header, bookmarks, export, nav, convo, convoOptions * fix: small style issues * fix: button * fix: bookmarks header menu * fix: dropdown close glitch * feat: Improve accessibility and keyboard navigation in ModelSpec component * fix: Nav related type issues * style: ConvoOptions theming and focus ring --------- Co-authored-by: Danny Avila <danny@librechat.ai>
100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import React from 'react';
|
|
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react';
|
|
|
|
interface DropdownProps {
|
|
trigger: React.ReactNode;
|
|
items: {
|
|
label?: string;
|
|
onClick?: () => void;
|
|
icon?: React.ReactNode;
|
|
kbd?: string;
|
|
show?: boolean;
|
|
disabled?: boolean;
|
|
separate?: boolean;
|
|
}[];
|
|
isOpen: boolean;
|
|
setIsOpen: (isOpen: boolean) => void;
|
|
className?: string;
|
|
anchor?: string;
|
|
}
|
|
|
|
const DropdownPopup: React.FC<DropdownProps> = ({
|
|
trigger,
|
|
items,
|
|
isOpen,
|
|
setIsOpen,
|
|
className,
|
|
anchor = { x: 'bottom', y: 'start' },
|
|
}) => {
|
|
const handleButtonClick = () => {
|
|
setIsOpen(!isOpen);
|
|
};
|
|
|
|
return (
|
|
<Menu>
|
|
{({ open }) => (
|
|
<>
|
|
<MenuButton
|
|
onClick={handleButtonClick}
|
|
className={`inline-flex items-center gap-2 rounded-md ${className}`}
|
|
>
|
|
{trigger}
|
|
</MenuButton>
|
|
<Transition
|
|
show={open}
|
|
enter="transition-opacity duration-150"
|
|
enterFrom="opacity-0"
|
|
enterTo="opacity-100"
|
|
leave="transition-opacity duration-150"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0"
|
|
afterLeave={() => setIsOpen(false)}
|
|
>
|
|
<div className={`${isOpen ? 'visible' : 'invisible'}`}>
|
|
{open && (
|
|
<MenuItems
|
|
static
|
|
// @ts-ignore
|
|
anchor={anchor}
|
|
className="mt-2 overflow-hidden rounded-lg bg-header-primary p-1.5 shadow-lg outline-none focus-visible:ring-2 focus-visible:ring-ring-primary"
|
|
>
|
|
<div>
|
|
{items
|
|
.filter((item) => item.show !== false)
|
|
.map((item, index) =>
|
|
item.separate ? (
|
|
<div key={index} className="my-1 h-px bg-white/10" />
|
|
) : (
|
|
<MenuItem key={index}>
|
|
<button
|
|
onClick={item.onClick}
|
|
className="group flex w-full gap-2 rounded-lg p-2.5 text-sm text-text-primary transition-colors duration-200 data-[focus]:bg-surface-hover"
|
|
disabled={item.disabled}
|
|
>
|
|
{item.icon && (
|
|
<span className="mr-2 h-5 w-5" aria-hidden="true">
|
|
{item.icon}
|
|
</span>
|
|
)}
|
|
{item.label}
|
|
{item.kbd && (
|
|
<kbd className="ml-auto hidden font-sans text-xs text-black/50 group-data-[focus]:inline dark:text-white/50">
|
|
⌘{item.kbd}
|
|
</kbd>
|
|
)}
|
|
</button>
|
|
</MenuItem>
|
|
),
|
|
)}
|
|
</div>
|
|
</MenuItems>
|
|
)}
|
|
</div>
|
|
</Transition>
|
|
</>
|
|
)}
|
|
</Menu>
|
|
);
|
|
};
|
|
|
|
export default DropdownPopup;
|