editor-context-menu.tsx 2.6 KB

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