editor-context-menu.tsx 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { FC, Fragment, memo, useEffect, useRef } from 'react'
  2. import ReactDOM from 'react-dom'
  3. import { getTooltip } from '@codemirror/view'
  4. import {
  5. Dropdown,
  6. DropdownMenu,
  7. DropdownItem,
  8. DropdownDivider,
  9. } from '@/shared/components/dropdown/dropdown-menu'
  10. import {
  11. useCodeMirrorStateContext,
  12. useCodeMirrorViewContext,
  13. } from './codemirror-context'
  14. import { contextMenuStateField } from '../extensions/context-menu'
  15. import { useFeatureFlag } from '@/shared/context/split-test-context'
  16. import { useContextMenuItems } from '../hooks/use-context-menu-items'
  17. import DropdownListItem from '@/shared/components/dropdown/dropdown-list-item'
  18. import { EditorContextMenuFeedback } from './editor-context-menu-feedback'
  19. import { sendContextMenuEvent } from '../utils/context-menu-analytics'
  20. const EditorContextMenu: FC = () => {
  21. const state = useCodeMirrorStateContext()
  22. const view = useCodeMirrorViewContext()
  23. const editorContextMenuEnabled = useFeatureFlag('editor-context-menu')
  24. const menuState = state.field(contextMenuStateField, false)
  25. if (!editorContextMenuEnabled || !menuState?.tooltip) {
  26. return null
  27. }
  28. const tooltipView = getTooltip(view, menuState.tooltip)
  29. if (!tooltipView) {
  30. return null
  31. }
  32. return ReactDOM.createPortal(<EditorContextMenuContent />, tooltipView.dom)
  33. }
  34. const EditorContextMenuContent: FC = memo(function EditorContextMenuContent() {
  35. const { menuItems, closeMenu, onToggle } = useContextMenuItems()
  36. const menuRef = useRef<any>(null)
  37. useEffect(() => {
  38. sendContextMenuEvent('menu-expand', {
  39. location: 'editor-context-menu',
  40. })
  41. menuRef.current?.focus()
  42. }, [])
  43. return (
  44. <Dropdown show onToggle={onToggle}>
  45. <DropdownMenu
  46. ref={menuRef}
  47. show
  48. tabIndex={0}
  49. className="dropdown-menu-unpositioned"
  50. onKeyDown={event => {
  51. switch (event.code) {
  52. case 'Escape':
  53. case 'Tab':
  54. event.preventDefault()
  55. closeMenu()
  56. break
  57. }
  58. }}
  59. >
  60. {menuItems.map((menuItem, index) => (
  61. <Fragment key={index}>
  62. {menuItem.separatorAbove && <DropdownDivider />}
  63. <DropdownListItem>
  64. <DropdownItem
  65. as="button"
  66. onClick={() => menuItem.handler()}
  67. disabled={menuItem.disabled}
  68. trailingIcon={
  69. menuItem.shortcut ? (
  70. <span>{menuItem.shortcut}</span>
  71. ) : undefined
  72. }
  73. >
  74. {menuItem.label}
  75. </DropdownItem>
  76. </DropdownListItem>
  77. </Fragment>
  78. ))}
  79. <EditorContextMenuFeedback />
  80. </DropdownMenu>
  81. </Dropdown>
  82. )
  83. })
  84. export default EditorContextMenu