editor-context-menu.tsx 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  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. const EditorContextMenu: FC = () => {
  19. const state = useCodeMirrorStateContext()
  20. const view = useCodeMirrorViewContext()
  21. const editorContextMenuEnabled = useFeatureFlag('editor-context-menu')
  22. const menuState = state.field(contextMenuStateField, false)
  23. if (!editorContextMenuEnabled || !menuState?.tooltip) {
  24. return null
  25. }
  26. const tooltipView = getTooltip(view, menuState.tooltip)
  27. if (!tooltipView) {
  28. return null
  29. }
  30. return ReactDOM.createPortal(<EditorContextMenuContent />, tooltipView.dom)
  31. }
  32. const EditorContextMenuContent: FC = memo(function EditorContextMenuContent() {
  33. const { menuItems, closeMenu, onToggle } = useContextMenuItems()
  34. const menuRef = useRef<any>(null)
  35. useEffect(() => {
  36. menuRef.current?.focus()
  37. }, [])
  38. return (
  39. <Dropdown show onToggle={onToggle}>
  40. <DropdownMenu
  41. ref={menuRef}
  42. show
  43. tabIndex={0}
  44. className="dropdown-menu-unpositioned"
  45. onKeyDown={event => {
  46. switch (event.code) {
  47. case 'Escape':
  48. case 'Tab':
  49. event.preventDefault()
  50. closeMenu()
  51. break
  52. }
  53. }}
  54. >
  55. {menuItems.map((menuItem, index) => (
  56. <Fragment key={index}>
  57. {menuItem.separatorAbove && <DropdownDivider />}
  58. <DropdownListItem>
  59. <DropdownItem
  60. as="button"
  61. onClick={() => menuItem.handler()}
  62. disabled={menuItem.disabled}
  63. trailingIcon={
  64. menuItem.shortcut ? (
  65. <span>{menuItem.shortcut}</span>
  66. ) : undefined
  67. }
  68. >
  69. {menuItem.label}
  70. </DropdownItem>
  71. </DropdownListItem>
  72. </Fragment>
  73. ))}
  74. </DropdownMenu>
  75. </Dropdown>
  76. )
  77. })
  78. export default EditorContextMenu