diff --git a/packages/frontend/component/package.json b/packages/frontend/component/package.json index 12acb6b3e1..8d053d4d9f 100644 --- a/packages/frontend/component/package.json +++ b/packages/frontend/component/package.json @@ -34,6 +34,7 @@ "@emotion/styled": "^11.14.0", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-collapsible": "^1.1.2", + "@radix-ui/react-context-menu": "^2.2.15", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-dropdown-menu": "^2.1.3", "@radix-ui/react-popover": "^1.1.3", diff --git a/packages/frontend/component/src/ui/menu/desktop/context-menu.tsx b/packages/frontend/component/src/ui/menu/desktop/context-menu.tsx new file mode 100644 index 0000000000..f44fed695d --- /dev/null +++ b/packages/frontend/component/src/ui/menu/desktop/context-menu.tsx @@ -0,0 +1,58 @@ +import * as RadixContextMenu from '@radix-ui/react-context-menu'; +import clsx from 'clsx'; +import type { RefAttributes } from 'react'; + +import * as styles from '../styles.css'; +import { DesktopMenuContext } from './context'; +import * as desktopStyles from './styles.css'; + +export type ContextMenuProps = RadixContextMenu.ContextMenuProps & + RadixContextMenu.ContextMenuTriggerProps & + RefAttributes & { + items: React.ReactNode; + contentProps?: RadixContextMenu.ContextMenuContentProps; + }; + +const ContextMenuContextValue = { + type: 'context-menu', +} as const; + +export const ContextMenu = ({ + children, + onOpenChange, + dir, + modal, + items, + contentProps, + ...props +}: ContextMenuProps) => { + return ( + + + + {children} + + + + {items} + + + + + ); +}; diff --git a/packages/frontend/component/src/ui/menu/desktop/context.ts b/packages/frontend/component/src/ui/menu/desktop/context.ts new file mode 100644 index 0000000000..8ec0662c73 --- /dev/null +++ b/packages/frontend/component/src/ui/menu/desktop/context.ts @@ -0,0 +1,9 @@ +import { createContext } from 'react'; + +interface DesktopMenuContextValue { + type: 'dropdown-menu' | 'context-menu'; +} + +export const DesktopMenuContext = createContext({ + type: 'dropdown-menu', +}); diff --git a/packages/frontend/component/src/ui/menu/desktop/item.tsx b/packages/frontend/component/src/ui/menu/desktop/item.tsx index 4d32afed2f..c1906ae137 100644 --- a/packages/frontend/component/src/ui/menu/desktop/item.tsx +++ b/packages/frontend/component/src/ui/menu/desktop/item.tsx @@ -1,13 +1,30 @@ +import * as ContextMenu from '@radix-ui/react-context-menu'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; +import { useContext } from 'react'; import type { MenuItemProps } from '../menu.types'; import { useMenuItem } from '../use-menu-item'; +import { DesktopMenuContext } from './context'; export const DesktopMenuItem = (props: MenuItemProps) => { + const { type } = useContext(DesktopMenuContext); const { className, children, otherProps } = useMenuItem(props); - return ( - - {children} - - ); + + if (type === 'dropdown-menu') { + return ( + + {children} + + ); + } + + if (type === 'context-menu') { + return ( + + {children} + + ); + } + + return null; }; diff --git a/packages/frontend/component/src/ui/menu/desktop/root.tsx b/packages/frontend/component/src/ui/menu/desktop/root.tsx index 0509c6af1f..5968e69c70 100644 --- a/packages/frontend/component/src/ui/menu/desktop/root.tsx +++ b/packages/frontend/component/src/ui/menu/desktop/root.tsx @@ -4,8 +4,13 @@ import React, { useCallback, useImperativeHandle, useState } from 'react'; import type { MenuProps } from '../menu.types'; import * as styles from '../styles.css'; +import { DesktopMenuContext } from './context'; import * as desktopStyles from './styles.css'; +const MenuContextValue = { + type: 'dropdown-menu', +} as const; + export const DesktopMenu = ({ children, items, @@ -53,37 +58,39 @@ export const DesktopMenu = ({ const ContentWrapper = noPortal ? React.Fragment : DropdownMenu.Portal; return ( - - { - e.stopPropagation(); - e.preventDefault(); - }} + + - {children} - - - - { + e.stopPropagation(); + e.preventDefault(); + }} > - {items} - - - + {children} + + + + + {items} + + + + ); }; diff --git a/packages/frontend/component/src/ui/menu/desktop/sub.tsx b/packages/frontend/component/src/ui/menu/desktop/sub.tsx index bda807970b..7a7dc0c8ed 100644 --- a/packages/frontend/component/src/ui/menu/desktop/sub.tsx +++ b/packages/frontend/component/src/ui/menu/desktop/sub.tsx @@ -1,11 +1,13 @@ import { ArrowRightSmallIcon } from '@blocksuite/icons/rc'; +import * as ContextMenu from '@radix-ui/react-context-menu'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import clsx from 'clsx'; -import { useMemo } from 'react'; +import { useContext, useMemo } from 'react'; import type { MenuSubProps } from '../menu.types'; import * as styles from '../styles.css'; import { useMenuItem } from '../use-menu-item'; +import { DesktopMenuContext } from './context'; export const DesktopMenuSub = ({ children: propsChildren, @@ -19,12 +21,37 @@ export const DesktopMenuSub = ({ ...otherSubContentOptions } = {}, }: MenuSubProps) => { + const { type } = useContext(DesktopMenuContext); const { className, children, otherProps } = useMenuItem({ children: propsChildren, suffixIcon: , ...triggerOptions, }); + const contentClassName = useMemo( + () => clsx(styles.menuContent, subContentClassName), + [subContentClassName] + ); + + if (type === 'context-menu') { + return ( + + + {children} + + + + {items} + + + + ); + } + return ( @@ -32,10 +59,7 @@ export const DesktopMenuSub = ({ clsx(styles.menuContent, subContentClassName), - [subContentClassName] - )} + className={contentClassName} style={{ zIndex: 'var(--affine-z-index-popover)', ...contentStyle }} {...otherSubContentOptions} > diff --git a/packages/frontend/component/src/ui/menu/index.ts b/packages/frontend/component/src/ui/menu/index.ts index a3ac2f0d6f..10727e4726 100644 --- a/packages/frontend/component/src/ui/menu/index.ts +++ b/packages/frontend/component/src/ui/menu/index.ts @@ -1,4 +1,5 @@ export * from './menu.types'; +import { ContextMenu } from './desktop/context-menu'; import { DesktopMenuItem } from './desktop/item'; import { DesktopMenu } from './desktop/root'; import { DesktopMenuSeparator } from './desktop/separator'; @@ -19,6 +20,7 @@ const MenuSub = BUILD_CONFIG.isMobileEdition ? MobileMenuSub : DesktopMenuSub; const Menu = BUILD_CONFIG.isMobileEdition ? MobileMenu : DesktopMenu; export { + ContextMenu, DesktopMenu, DesktopMenuItem, DesktopMenuSeparator, diff --git a/packages/frontend/core/package.json b/packages/frontend/core/package.json index 854d3a7778..e49f8fff8e 100644 --- a/packages/frontend/core/package.json +++ b/packages/frontend/core/package.json @@ -34,6 +34,7 @@ "@marsidev/react-turnstile": "^1.1.0", "@preact/signals-core": "^1.8.0", "@radix-ui/react-collapsible": "^1.1.2", + "@radix-ui/react-context-menu": "^2.1.15", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-popover": "^1.1.3", "@radix-ui/react-scroll-area": "^1.2.2", diff --git a/packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx b/packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx index c6ad29c28b..335399f417 100644 --- a/packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx +++ b/packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx @@ -1,5 +1,6 @@ import { Checkbox, + ContextMenu, DragHandle as DragHandleIcon, Tooltip, useDraggable, @@ -29,7 +30,7 @@ import { PagePreview } from '../../page-list/page-content-preview'; import { DocExplorerContext } from '../context'; import { quickActions } from '../quick-actions.constants'; import * as styles from './doc-list-item.css'; -import { MoreMenuButton } from './more-menu'; +import { MoreMenuButton, MoreMenuContent } from './more-menu'; import { CardViewProperties, ListViewProperties } from './properties'; export type DocListItemView = 'list' | 'grid' | 'masonry'; @@ -314,38 +315,46 @@ export const ListViewDoc = ({ docId }: DocListItemProps) => { const t = useI18n(); const docsService = useService(DocsService); const doc = useLiveData(docsService.list.doc$(docId)); + const contextValue = useContext(DocExplorerContext); + const showMoreOperation = useLiveData(contextValue.showMoreOperation$); if (!doc) { return null; } return ( -
  • - - + +
    + + +
    +
    + + {quickActions.map(action => { + return ( + + + + ); + })} + - -
    -
    - - {quickActions.map(action => { - return ( - - - - ); - })} - -
  • + + ); }; diff --git a/packages/frontend/core/src/desktop/components/navigation-panel/tree/node.tsx b/packages/frontend/core/src/desktop/components/navigation-panel/tree/node.tsx index 26c084c2df..260ef4ba03 100644 --- a/packages/frontend/core/src/desktop/components/navigation-panel/tree/node.tsx +++ b/packages/frontend/core/src/desktop/components/navigation-panel/tree/node.tsx @@ -1,4 +1,5 @@ import { + ContextMenu, DropIndicator, type DropTargetDropEvent, type DropTargetOptions, @@ -466,47 +467,54 @@ export const NavigationPanelTreeNode = ({ ref={rootRef} {...otherProps} > -
    ( + {view} + ))} > - {to ? ( - - {content} - - ) : ( -
    {content}
    - )} - -
    {content}
    -
    - {treeInstruction && - // Do not show drop indicator for self dragged over - !(treeInstruction.type !== 'reparent' && isSelfDraggedOver) && - treeInstruction.type !== 'instruction-blocked' && ( - +
    + {to ? ( + + {content} + + ) : ( +
    {content}
    )} - {draggedOver && - dropEffect && - draggedOverPosition && - !isSelfDraggedOver && - draggedOverDraggable && ( - - )} -
    + +
    {content}
    +
    + {treeInstruction && + // Do not show drop indicator for self dragged over + !(treeInstruction.type !== 'reparent' && isSelfDraggedOver) && + treeInstruction.type !== 'instruction-blocked' && ( + + )} + {draggedOver && + dropEffect && + draggedOverPosition && + !isSelfDraggedOver && + draggedOverDraggable && ( + + )} +
    + {/* For lastInGroup check, the placeholder must be placed above all children in the dom */}
    diff --git a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx index 88b7a0a994..37d0c5359e 100644 --- a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx +++ b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx @@ -70,6 +70,9 @@ export const WorkbenchLink = forwardRef( if (event.defaultPrevented) { return; } + if (event.button !== 0 && event.button !== 1) { + return; + } const at = inferOpenAt(event); workbench.open(to, { at, replaceHistory, show: false }); event.preventDefault(); diff --git a/yarn.lock b/yarn.lock index 8b3facbb59..229fc1faa7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -320,6 +320,7 @@ __metadata: "@emotion/styled": "npm:^11.14.0" "@radix-ui/react-avatar": "npm:^1.1.2" "@radix-ui/react-collapsible": "npm:^1.1.2" + "@radix-ui/react-context-menu": "npm:^2.2.15" "@radix-ui/react-dialog": "npm:^1.1.3" "@radix-ui/react-dropdown-menu": "npm:^2.1.3" "@radix-ui/react-popover": "npm:^1.1.3" @@ -418,6 +419,7 @@ __metadata: "@marsidev/react-turnstile": "npm:^1.1.0" "@preact/signals-core": "npm:^1.8.0" "@radix-ui/react-collapsible": "npm:^1.1.2" + "@radix-ui/react-context-menu": "npm:^2.1.15" "@radix-ui/react-dialog": "npm:^1.1.3" "@radix-ui/react-popover": "npm:^1.1.3" "@radix-ui/react-scroll-area": "npm:^1.2.2" @@ -11238,7 +11240,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-context-menu@npm:^2.2.3": +"@radix-ui/react-context-menu@npm:^2.1.15, @radix-ui/react-context-menu@npm:^2.2.15, @radix-ui/react-context-menu@npm:^2.2.3": version: 2.2.15 resolution: "@radix-ui/react-context-menu@npm:2.2.15" dependencies: