button-menu.tsx 3.0 KB

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