button-menu.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import { FC, memo, useEffect, useRef } from 'react'
  2. import useDropdown from '../../../../shared/hooks/use-dropdown'
  3. import OLListGroup from '@/shared/components/ol/ol-list-group'
  4. import OLTooltip from '@/shared/components/ol/ol-tooltip'
  5. import OLOverlay from '@/shared/components/ol/ol-overlay'
  6. import OLPopover from '@/shared/components/ol/ol-popover'
  7. import { EditorView } from '@codemirror/view'
  8. import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
  9. import { useCodeMirrorViewContext } from '../codemirror-context'
  10. export const ToolbarButtonMenu: FC<
  11. React.PropsWithChildren<{
  12. id: string
  13. label: string
  14. icon: React.ReactNode
  15. disabled?: boolean
  16. disablePopover?: boolean
  17. altCommand?: (view: EditorView) => void
  18. onToggle?: (isOpen: boolean) => void
  19. }>
  20. > = memo(function ButtonMenu({
  21. icon,
  22. id,
  23. label,
  24. altCommand,
  25. onToggle,
  26. disabled,
  27. disablePopover,
  28. children,
  29. }) {
  30. const target = useRef<any>(null)
  31. const { open, onToggle: handleToggle, ref } = useDropdown()
  32. const view = useCodeMirrorViewContext()
  33. useEffect(() => {
  34. if (disablePopover && open) {
  35. handleToggle(false)
  36. }
  37. }, [open, disablePopover, handleToggle])
  38. useEffect(() => {
  39. onToggle?.(open)
  40. }, [open, onToggle])
  41. const button = (
  42. <button
  43. type="button"
  44. className="ol-cm-toolbar-button"
  45. aria-label={label}
  46. aria-disabled={disabled}
  47. onMouseDown={event => {
  48. event.preventDefault()
  49. event.stopPropagation()
  50. }}
  51. onClick={event => {
  52. if (disabled) {
  53. event.preventDefault()
  54. return
  55. }
  56. if (event.altKey && altCommand && open === false) {
  57. emitToolbarEvent(view, id)
  58. event.preventDefault()
  59. altCommand(view)
  60. view.focus()
  61. } else {
  62. handleToggle(!open)
  63. }
  64. }}
  65. ref={target}
  66. >
  67. {icon}
  68. </button>
  69. )
  70. const overlay = (
  71. <OLOverlay
  72. show={open && !disablePopover}
  73. target={target.current}
  74. placement="bottom"
  75. container={view.dom}
  76. containerPadding={0}
  77. transition
  78. rootClose
  79. onHide={() => handleToggle(false)}
  80. >
  81. <OLPopover
  82. id={`${id}-menu`}
  83. ref={ref}
  84. className="ol-cm-toolbar-button-menu-popover"
  85. >
  86. <OLListGroup
  87. role="menu"
  88. onClick={() => {
  89. handleToggle(false)
  90. }}
  91. >
  92. {children}
  93. </OLListGroup>
  94. </OLPopover>
  95. </OLOverlay>
  96. )
  97. if (!label) {
  98. return (
  99. <>
  100. {button}
  101. {overlay}
  102. </>
  103. )
  104. }
  105. return (
  106. <>
  107. <OLTooltip
  108. hidden={open}
  109. id={id}
  110. description={<div>{label}</div>}
  111. overlayProps={{ placement: 'bottom' }}
  112. >
  113. {button}
  114. </OLTooltip>
  115. {overlay}
  116. </>
  117. )
  118. })