mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-21 02:40:14 +01:00
🖼️ style: Conversation Menu and Dialogs update (#3601)
* 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>
This commit is contained in:
parent
7f50d2f7c0
commit
96581d56df
62 changed files with 2627 additions and 1821 deletions
100
client/src/components/ui/DropdownPopup.tsx
Normal file
100
client/src/components/ui/DropdownPopup.tsx
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue