/* * Copyright 2023 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import classNames from "classnames"; import { memo, useCallback } from "react"; import { Classes, DISPLAYNAME_PREFIX } from "../../common"; import type { PopoverTargetProps } from "../popover/popoverSharedProps"; import { PopoverNext } from "../popover-next/popoverNext"; import { popoverPropsToNextProps } from "../popover-next/popoverNextMigrationUtils"; import { Portal } from "../portal/portal"; import type { ContextMenuPopoverOptions, Offset } from "./contextMenuShared"; export interface ContextMenuPopoverProps extends ContextMenuPopoverOptions { isOpen: boolean; isDarkTheme?: boolean; content: React.JSX.Element; onClose?: () => void; targetOffset: Offset | undefined; } /** * A floating popover which is positioned at a given target offset inside its parent element container. * Used to display context menus. Note that this behaves differently from other popover components like * Popover and Tooltip, which wrap their children with interaction handlers -- if you're looking for the whole * interaction package, use ContextMenu instead. * * @see https://blueprintjs.com/docs/#core/components/context-menu-popover */ export const ContextMenuPopover = memo(function ContextMenuPopover(props: ContextMenuPopoverProps) { const { content, popoverClassName, onClose, isDarkTheme = false, rootBoundary = "viewport", targetOffset, transitionDuration = 100, ...popoverProps } = props; const cancelContextMenu = useCallback((e: React.SyntheticEvent) => e.preventDefault(), []); // Popover should attach its ref to the virtual target we render inside a Portal, not the "inline" child target const renderTarget = useCallback( ({ ref }: PopoverTargetProps) => (
), [targetOffset], ); const handleInteraction = useCallback( (nextOpenState: boolean) => { if (!nextOpenState) { onClose?.(); } }, [onClose], ); return ( {content}
} enforceFocus={false} // Generate key based on offset so that a new Popover instance is created // when offset changes, to force recomputing position. key={getPopoverKey(targetOffset)} hasBackdrop={true} backdropProps={{ className: Classes.CONTEXT_MENU_BACKDROP }} animation="minimal" arrow={false} onInteraction={handleInteraction} popoverClassName={classNames(Classes.CONTEXT_MENU_POPOVER, popoverClassName, { [Classes.DARK]: isDarkTheme, })} positioningStrategy="fixed" renderTarget={renderTarget} /> ); }); ContextMenuPopover.displayName = `${DISPLAYNAME_PREFIX}.ContextMenuPopover`; function getPopoverKey(targetOffset: Offset | undefined) { return targetOffset === undefined ? "default" : `${targetOffset.left}x${targetOffset.top}`; }