Popups: improvements v2

This commit is contained in:
Enrico Ros
2024-10-21 09:26:23 -07:00
parent 35733e86b9
commit bae15f3bb6
13 changed files with 196 additions and 174 deletions
@@ -2,7 +2,7 @@ import * as React from 'react';
import { Box, ListItem, ListItemButton, ListItemDecorator, Sheet, Typography } from '@mui/joy';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import type { ActileItem, ActileProvider } from './ActileProvider';
@@ -33,12 +33,12 @@ export function ActilePopup(props: {
}, [props.itemsByProvider, props.activeItemIndex]);
return (
<CloseableMenu
open anchorEl={props.anchorEl} onClose={props.onClose}
noTopPadding
noBottomPadding
<CloseablePopup
menu anchorEl={props.anchorEl} onClose={props.onClose}
maxHeightGapPx={320}
sx={{ minWidth: 320 }}
minWidth={320}
noBottomPadding
noTopPadding
>
{!props.itemsByProvider.length && (
@@ -108,6 +108,6 @@ export function ActilePopup(props: {
</React.Fragment>
))}
</CloseableMenu>
</CloseablePopup>
);
}
@@ -15,7 +15,7 @@ import ReadMoreIcon from '@mui/icons-material/ReadMore';
import VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom';
import VisibilityIcon from '@mui/icons-material/Visibility';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { DMessageAttachmentFragment, DMessageDocPart, DMessageImageRefPart, isDocPart, isImageRefPart } from '~/common/stores/chat/chat.fragments';
import { LiveFileIcon } from '~/common/livefile/liveFile.icons';
import { copyToClipboard } from '~/common/util/clipboardUtils';
@@ -151,11 +151,13 @@ export function LLMAttachmentMenu(props: {
const showInputs = uiComplexityMode !== 'minimal';
return (
<CloseableMenu
dense placement='top'
<CloseablePopup
menu anchorEl={props.menuAnchor} onClose={props.onClose}
dense
maxWidth={460}
minWidth={260}
noTopPadding
open anchorEl={props.menuAnchor} onClose={props.onClose}
sx={{ minWidth: 260, maxWidth: 460 }}
placement='top'
>
{/* Move Arrows */}
@@ -388,6 +390,6 @@ export function LLMAttachmentMenu(props: {
Remove
</MenuItem>
</CloseableMenu>
</CloseablePopup>
);
}
@@ -9,7 +9,7 @@ import VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom';
import type { AgiAttachmentPromptsData } from '~/modules/aifn/agiattachmentprompts/useAgiAttachmentPrompts';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { ConfirmationModal } from '~/common/components/modals/ConfirmationModal';
import { useOverlayComponents } from '~/common/layout/overlays/useOverlayComponents';
@@ -205,11 +205,11 @@ export function LLMAttachmentsList(props: {
{/* All Drafts Menu */}
{!!overallMenuAnchor && (
<CloseableMenu
open
dense placement='top-start'
anchorEl={overallMenuAnchor} onClose={handleOverallMenuHide}
sx={{ minWidth: 200 }}
<CloseablePopup
menu anchorEl={overallMenuAnchor} onClose={handleOverallMenuHide}
dense
minWidth={200}
placement='top-start'
>
{/* uses the agiAttachmentPrompts to imagine what the user will ask aboud those */}
<MenuItem color='primary' variant='soft' onClick={agiAttachmentPrompts.refetch} disabled={!hasAttachments || agiAttachmentPrompts.isFetching}>
@@ -234,7 +234,7 @@ export function LLMAttachmentsList(props: {
<ListItemDecorator><ClearIcon /></ListItemDecorator>
Remove All{llmAttachmentDrafts.length > 5 ? <span style={{ opacity: 0.5 }}> {llmAttachmentDrafts.length} attachments</span> : null}
</MenuItem>
</CloseableMenu>
</CloseablePopup>
)}
</>;
@@ -15,7 +15,7 @@ import MoreVertIcon from '@mui/icons-material/MoreVert';
import StarOutlineRoundedIcon from '@mui/icons-material/StarOutlineRounded';
import type { DConversationId } from '~/common/stores/chat/chat.conversation';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { DFolder, useFolderStore } from '~/common/stores/folders/store-chat-folders';
import { DebouncedInputMemo } from '~/common/components/DebouncedInput';
import { FoldersToggleOff } from '~/common/components/icons/FoldersToggleOff';
@@ -410,12 +410,12 @@ function ChatDrawer(props: {
{/* [Menu] Chat Item Folder Change */}
{!!folderChangeRequest?.anchorEl && (
<CloseableMenu
<CloseablePopup
menu anchorEl={folderChangeRequest.anchorEl} onClose={handleConversationFolderCancel}
bigIcons
open anchorEl={folderChangeRequest.anchorEl} onClose={handleConversationFolderCancel}
minWidth={200}
placement='bottom-start'
zIndex={themeZIndexOverMobileDrawer /* need to be on top of the Modal on Mobile */}
sx={{ minWidth: 200 }}
>
{/* Folder Assignment Buttons */}
@@ -449,7 +449,7 @@ function ChatDrawer(props: {
</ListItem>
)}
</CloseableMenu>
</CloseablePopup>
)}
</>;
@@ -9,7 +9,7 @@ import EditRoundedIcon from '@mui/icons-material/EditRounded';
import FolderIcon from '@mui/icons-material/Folder';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { DFolder, FOLDERS_COLOR_PALETTE, useFolderStore } from '~/common/stores/folders/store-chat-folders';
import { InlineTextarea } from '~/common/components/InlineTextarea';
import { themeZIndexOverMobileDrawer } from '~/common/app.theme';
@@ -203,11 +203,12 @@ export function FolderListItem(props: {
</IconButton>
{!!menuAnchorEl && (
<CloseableMenu
dense placement='top'
open anchorEl={menuAnchorEl} onClose={handleMenuClose}
<CloseablePopup
menu anchorEl={menuAnchorEl} onClose={handleMenuClose}
dense
minWidth={200}
placement='top'
zIndex={themeZIndexOverMobileDrawer /* need to be on top of the Modal on Mobile */}
sx={{ minWidth: 200 }}
>
<MenuItem
@@ -316,7 +317,7 @@ export function FolderListItem(props: {
</RadioGroup>
</MenuItem>
</CloseableMenu>
</CloseablePopup>
)}
</ListItemButton>
@@ -35,7 +35,7 @@ import { ModelVendorAnthropic } from '~/modules/llms/vendors/anthropic/anthropic
import { AnthropicIcon } from '~/common/components/icons/vendors/AnthropicIcon';
import { ChatBeamIcon } from '~/common/components/icons/ChatBeamIcon';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { DMessage, DMessageId, DMessageUserFlag, DMetaReferenceItem, MESSAGE_FLAG_AIX_SKIP, MESSAGE_FLAG_NOTIFY_COMPLETE, MESSAGE_FLAG_STARRED, MESSAGE_FLAG_VND_ANT_CACHE_AUTO, MESSAGE_FLAG_VND_ANT_CACHE_USER, messageFragmentsReduceText, messageHasUserFlag } from '~/common/stores/chat/chat.message';
import { KeyStroke } from '~/common/components/KeyStroke';
import { MarkHighlightIcon } from '~/common/components/icons/MarkHighlightIcon';
@@ -796,10 +796,11 @@ export function ChatMessage(props: {
{/* Message Operations Menu (3 dots) */}
{!!opsMenuAnchor && (
<CloseableMenu
dense placement='auto-end'
open={true} anchorEl={opsMenuAnchor} onClose={handleCloseOpsMenu}
sx={{ minWidth: 280 }}
<CloseablePopup
menu anchorEl={opsMenuAnchor} onClose={handleCloseOpsMenu}
dense
minWidth={280}
placement={fromAssistant ? 'auto-start' : 'auto-end'}
>
{fromSystem && (
@@ -946,7 +947,7 @@ export function ChatMessage(props: {
: <Box sx={{ flexGrow: 1, display: 'flex', justifyContent: 'space-between', gap: 1 }}>Beam Edit<KeyStroke variant='outlined' combo='Ctrl + Shift + B' /></Box>}
</MenuItem>
)}
</CloseableMenu>
</CloseablePopup>
)}
@@ -1049,10 +1050,11 @@ export function ChatMessage(props: {
{/* Context (Right-click) Menu */}
{!!contextMenuAnchor && (
<CloseableMenu
dense placement='bottom-start'
open={true} anchorEl={contextMenuAnchor} onClose={closeContextMenu}
sx={{ minWidth: 220 }}
<CloseablePopup
menu anchorEl={contextMenuAnchor} onClose={closeContextMenu}
dense
minWidth={220}
placement='bottom-start'
>
<MenuItem onClick={handleOpsCopy} sx={{ flex: 1, alignItems: 'center' }}>
<ListItemDecorator><ContentCopyIcon /></ListItemDecorator>
@@ -1071,7 +1073,7 @@ export function ChatMessage(props: {
<ListItemDecorator>{props.isSpeaking ? <CircularProgress size='sm' /> : <RecordVoiceOverOutlinedIcon />}</ListItemDecorator>
Speak
</MenuItem>}
</CloseableMenu>
</CloseablePopup>
)}
</Box>
@@ -2,7 +2,7 @@ import * as React from 'react';
import { Box, MenuItem, Radio, Typography } from '@mui/joy';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { KeyStroke, platformAwareKeystrokes } from '~/common/components/KeyStroke';
import { useUIPreferencesStore } from '~/common/state/store-ui';
@@ -23,10 +23,10 @@ export function ExecuteModeMenu(props: {
const enterIsNewline = useUIPreferencesStore(state => state.enterIsNewline);
return (
<CloseableMenu
<CloseablePopup
menu anchorEl={props.anchorEl} onClose={props.onClose}
minWidth={320}
placement='top-end'
open={true} anchorEl={props.anchorEl} onClose={props.onClose}
sx={{ minWidth: 320 }}
>
{/*<MenuItem color='neutral' selected>*/}
@@ -60,7 +60,7 @@ export function ExecuteModeMenu(props: {
</MenuItem>,
)}
</CloseableMenu>
</CloseablePopup>
);
}
-100
View File
@@ -1,100 +0,0 @@
import * as React from 'react';
import { ClickAwayListener, Popper, PopperPlacementType } from '@mui/base';
import { MenuList, styled } from '@mui/joy';
import { SxProps } from '@mui/joy/styles/types';
// adds the 'sx' prop to the Popper, and defaults zIndex to 1000
const Popup = styled(Popper)({
zIndex: 1000,
});
/**
* Workaround to the Menu in Joy 5-beta.0.
*
* This component addresses major changes in the Menu component in Joy 5-beta.0:
* - missing callback for onClose
* - clickaway listener not working
* - dynamic menus unsupported
* - ...
*/
export function CloseableMenu(props: {
open: boolean, anchorEl: HTMLElement | null, onClose: () => void,
dense?: boolean,
bigIcons?: boolean,
// variant?: VariantProp,
// color?: ColorPaletteProp,
// size?: 'sm' | 'md' | 'lg',
placement?: PopperPlacementType,
placementOffset?: number[],
maxHeightGapPx?: number,
noTopPadding?: boolean,
noBottomPadding?: boolean,
sx?: SxProps,
zIndex?: number,
listRef?: React.Ref<HTMLUListElement>,
children?: React.ReactNode,
}) {
const handleClose = (event: MouseEvent | TouchEvent | React.KeyboardEvent) => {
event.stopPropagation();
props.onClose();
};
const handleListKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Tab') {
handleClose(event);
} else if (event.key === 'Escape') {
if (props.anchorEl)
props.anchorEl?.focus();
handleClose(event);
}
};
return (
<Popup
role={undefined}
open={props.open && props.anchorEl !== null}
anchorEl={props.anchorEl}
placement={props.placement}
disablePortal={false}
modifiers={[{
name: 'offset',
options: {
offset: props.placementOffset || [0, 4],
},
}]}
sx={props.zIndex
? { zIndex: props.zIndex }
: {}
}
>
<ClickAwayListener onClickAway={handleClose}>
<MenuList
ref={props.listRef}
// variant={props.variant} color={props.color}
onKeyDown={handleListKeyDown}
sx={{
'--ListItem-minHeight': props.dense
? '2.25rem' /* 2.25 is the default */
: '2.5rem', /* we enlarge the default */
...(props.bigIcons && {
'--Icon-fontSize': 'var(--joy-fontSize-xl2)',
// '--ListItemDecorator-size': '2.75rem',
}),
backgroundColor: 'background.popup',
boxShadow: 'md',
...(props.maxHeightGapPx !== undefined ? { maxHeight: `calc(100dvh - ${props.maxHeightGapPx}px)`, overflowY: 'auto' } : {}),
...(props.noTopPadding ? { pt: 0 } : {}),
...(props.noBottomPadding ? { pb: 0 } : {}),
...(props.sx || {}),
}}
>
{props.children}
</MenuList>
</ClickAwayListener>
</Popup>
);
}
+120
View File
@@ -0,0 +1,120 @@
import * as React from 'react';
import type { SxProps } from '@mui/joy/styles/types';
import { Box, MenuList, styled } from '@mui/joy';
import { ClickAwayListener, Popper, PopperPlacementType } from '@mui/base';
// adds the 'sx' prop to the Popper, and defaults zIndex to 1000
const Popup = styled(Popper)({
zIndex: 1000,
});
/**
* Workaround to the Menu in Joy 5-beta.0.
*
* This component addresses major changes in the Menu component in Joy 5-beta.0:
* - missing callback for onClose
* - clickaway listener not working
* - dynamic menus unsupported
* - ...
*/
export function CloseablePopup(props: {
menu?: boolean, // whether to render as a MenuList (or as a Box otherwise)
anchorEl: HTMLElement | null,
onClose: () => void,
// looks
dense?: boolean,
bigIcons?: boolean,
placement?: PopperPlacementType,
maxHeightGapPx?: number,
noTopPadding?: boolean,
noBottomPadding?: boolean,
minWidth?: number,
maxWidth?: number,
zIndex?: number,
sx?: SxProps,
// unused
placementOffset?: number[],
children?: React.ReactNode,
}) {
const handleClose = (event: MouseEvent | TouchEvent | React.KeyboardEvent) => {
event.stopPropagation();
props.onClose();
};
const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Tab') {
handleClose(event);
} else if (event.key === 'Escape') {
if (props.anchorEl)
props.anchorEl?.focus();
handleClose(event);
}
};
// memos
const modifiersMemo = React.useMemo(() => [{
name: 'offset',
options: {
offset: props.placementOffset || [0, 4],
},
}], [props.placementOffset]);
const styleMemoSx: SxProps = React.useMemo(() => ({
// style
backgroundColor: 'background.popup',
boxShadow: 'md',
...(props.maxHeightGapPx !== undefined ? { maxHeight: `calc(100dvh - ${props.maxHeightGapPx}px)`, overflowY: 'auto' } : {}),
...(props.maxWidth !== undefined && { maxWidth: props.maxWidth }),
...(props.minWidth !== undefined && { minWidth: props.minWidth }),
// MenuList customizations
'--ListItem-minHeight': props.dense
? '2.25rem' /* 2.25 is the default */
: '2.5rem', /* we enlarge the default */
...(props.bigIcons && {
'--Icon-fontSize': 'var(--joy-fontSize-xl2)',
// '--ListItemDecorator-size': '2.75rem',
}),
...(props.noBottomPadding && { pb: 0 }),
...(props.noTopPadding && { pt: 0 }),
// inject
...(props.sx || {}),
}), [props.dense, props.bigIcons, props.maxHeightGapPx, props.maxWidth, props.minWidth, props.noBottomPadding, props.noTopPadding, props.sx]);
return (
<Popup
role={undefined}
open={!!props.anchorEl}
anchorEl={props.anchorEl}
placement={props.placement}
disablePortal={false}
modifiers={modifiersMemo}
sx={props.zIndex ? { zIndex: props.zIndex } : undefined}
>
<ClickAwayListener onClickAway={handleClose}>
{props.menu ? (
<MenuList onKeyDown={handleKeyDown} sx={styleMemoSx}>
{props.children}
</MenuList>
) : (
<Box onKeyDown={handleKeyDown} sx={styleMemoSx}>
{props.children}
</Box>
)}
</ClickAwayListener>
</Popup>
);
}
+5 -5
View File
@@ -9,7 +9,7 @@ import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import { AgiSquircleIcon } from '~/common/components/icons/AgiSquircleIcon';
import { Brand } from '~/common/app.config';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { LayoutSidebarRight } from '~/common/components/icons/LayoutSidebarRight';
import { Link } from '~/common/components/Link';
import { ROUTE_INDEX } from '~/common/app.routes';
@@ -124,18 +124,18 @@ function RenderAsPopupDesktopOnly(props: {
return null;
return (
<CloseableMenu
open={true} anchorEl={props.menuAnchor.current} onClose={optimaCloseAppMenu}
<CloseablePopup
menu anchorEl={props.menuAnchor.current} onClose={optimaCloseAppMenu}
dense
maxHeightGapPx={56 + 24}
minWidth={280}
placement='bottom-end'
sx={{ minWidth: 280 }}
>
{/* contents rendered in a desktop popup menu */}
{props.menuContent}
</CloseableMenu>
</CloseablePopup>
);
}
@@ -5,7 +5,7 @@ import ClearIcon from '@mui/icons-material/Clear';
import CodeIcon from '@mui/icons-material/Code';
import type { LiveFileId, LiveFileMetadata } from '~/common/livefile/liveFile.types';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { LiveFileChooseIcon, LiveFileIcon } from '~/common/livefile/liveFile.icons';
import { TooltipOutlined } from '~/common/components/TooltipOutlined';
import { getFirstFileSystemFileHandle } from '~/common/util/fileSystemUtils';
@@ -146,14 +146,10 @@ export function WorkspaceLiveFilePicker(props: {
{/* Select/Upload file menu */}
{!!menuAnchor && (
<CloseableMenu
open
anchorEl={menuAnchor}
onClose={handleCloseMenu}
noTopPadding
noBottomPadding
placement='bottom-start'
sx={{ minWidth: 240 }}
<CloseablePopup
menu anchorEl={menuAnchor} onClose={handleCloseMenu}
placement='bottom-end'
sx={{ '--ListItem-paddingRight': '1.5rem' }}
>
{/* Workspace Files (if any) */}
@@ -198,7 +194,7 @@ export function WorkspaceLiveFilePicker(props: {
</MenuItem>
)}
</CloseableMenu>
</CloseablePopup>
)}
</>;
@@ -9,7 +9,7 @@ import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
import SaveAsOutlinedIcon from '@mui/icons-material/SaveAsOutlined';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { copyToClipboard } from '~/common/util/clipboardUtils';
import { isLiveFileSupported } from '~/common/livefile/store-live-file';
import { reverseLookupMdTitle, reverseLookupMimeType } from '~/common/attachment-drafts/attachment.mimetypes';
@@ -110,11 +110,11 @@ export function EnhancedRenderCodeMenu(props: {
return (
<CloseableMenu
open={true} dense
anchorEl={props.anchor} onClose={props.onClose}
<CloseablePopup
menu anchorEl={props.anchor} onClose={props.onClose}
dense
minWidth={250}
placement='bottom-end'
sx={{ minWidth: 250 }}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
@@ -155,6 +155,6 @@ export function EnhancedRenderCodeMenu(props: {
{/* </MenuItem>*/}
{/*)}*/}
</CloseableMenu>
</CloseablePopup>
);
}
@@ -5,7 +5,7 @@ import AddIcon from '@mui/icons-material/Add';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import type { DModelsService, DModelsServiceId } from '~/common/stores/llms/modelsservice.types';
import { CloseableMenu } from '~/common/components/CloseableMenu';
import { CloseablePopup } from '~/common/components/CloseablePopup';
import { ConfirmationModal } from '~/common/components/modals/ConfirmationModal';
import { llmsStoreActions, llmsStoreState } from '~/common/stores/llms/store-llms';
import { themeZIndexOverMobileDrawer } from '~/common/app.theme';
@@ -218,13 +218,14 @@ export function ModelsServiceSelector(props: {
{/* vendors popup, for adding */}
<CloseableMenu
placement='bottom-start' zIndex={themeZIndexOverMobileDrawer}
open={!!vendorsMenuAnchor} anchorEl={vendorsMenuAnchor} onClose={closeVendorsMenu}
sx={{ minWidth: 200 }}
<CloseablePopup
menu anchorEl={vendorsMenuAnchor} onClose={closeVendorsMenu}
minWidth={200}
placement='auto-end'
zIndex={themeZIndexOverMobileDrawer}
>
{vendorComponents}
</CloseableMenu>
</CloseablePopup>
</Box>
);