overflow.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { FC, useCallback, useEffect, useRef } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import classnames from 'classnames'
  4. import MaterialIcon from '@/shared/components/material-icon'
  5. import { useCodeMirrorViewContext } from '../codemirror-context'
  6. import OLOverlay from '@/features/ui/components/ol/ol-overlay'
  7. import OLPopover from '@/features/ui/components/ol/ol-popover'
  8. export const ToolbarOverflow: FC<{
  9. overflowed: boolean
  10. overflowOpen: boolean
  11. setOverflowOpen: (open: boolean) => void
  12. overflowRef?: React.Ref<HTMLDivElement>
  13. }> = ({ overflowed, overflowOpen, setOverflowOpen, overflowRef, children }) => {
  14. const { t } = useTranslation()
  15. const buttonRef = useRef<HTMLButtonElement>(null)
  16. const keyboardInputRef = useRef(false)
  17. const view = useCodeMirrorViewContext()
  18. const className = classnames(
  19. 'ol-cm-toolbar-button',
  20. 'ol-cm-toolbar-overflow-toggle',
  21. {
  22. 'ol-cm-toolbar-overflow-toggle-visible': overflowed,
  23. }
  24. )
  25. // A11y - Move the focus inside the popover to the first toolbar button when it opens
  26. const handlePopoverFocus = useCallback(() => {
  27. if (keyboardInputRef.current) {
  28. const firstToolbarItem = document.querySelector(
  29. '#popover-toolbar-overflow .ol-cm-toolbar-overflow button:not([disabled])'
  30. ) as HTMLButtonElement | null
  31. if (firstToolbarItem) {
  32. firstToolbarItem.focus()
  33. }
  34. }
  35. }, [])
  36. const handleKeyDown = useCallback(() => {
  37. keyboardInputRef.current = true
  38. }, [])
  39. const handleMouseDown = useCallback(() => {
  40. keyboardInputRef.current = false
  41. }, [])
  42. useEffect(() => {
  43. document.addEventListener('keydown', handleKeyDown)
  44. document.addEventListener('mousedown', handleMouseDown)
  45. return () => {
  46. document.removeEventListener('keydown', handleKeyDown)
  47. document.removeEventListener('mousedown', handleMouseDown)
  48. }
  49. }, [handleKeyDown, handleMouseDown])
  50. // A11y - Move the focus back to the trigger when the popover is dismissed
  51. const handleCloseAndReturnFocus = useCallback(() => {
  52. setOverflowOpen(false)
  53. if (keyboardInputRef.current && buttonRef.current) {
  54. buttonRef.current.focus()
  55. }
  56. }, [setOverflowOpen])
  57. return (
  58. <>
  59. <button
  60. ref={buttonRef}
  61. type="button"
  62. id="toolbar-more"
  63. className={className}
  64. aria-label={t('more_editor_toolbar_item')}
  65. aria-expanded={overflowOpen}
  66. aria-controls="popover-toolbar-overflow"
  67. onMouseDown={event => {
  68. event.preventDefault()
  69. event.stopPropagation()
  70. }}
  71. onClick={() => {
  72. setOverflowOpen(!overflowOpen)
  73. }}
  74. >
  75. <MaterialIcon type="more_horiz" />
  76. </button>
  77. <OLOverlay
  78. show={overflowOpen}
  79. target={buttonRef.current}
  80. placement="bottom"
  81. container={view.dom}
  82. // containerPadding={0}
  83. transition
  84. rootClose
  85. onHide={handleCloseAndReturnFocus}
  86. onEntered={handlePopoverFocus}
  87. >
  88. <OLPopover
  89. id="popover-toolbar-overflow"
  90. ref={overflowRef}
  91. role="toolbar"
  92. >
  93. <div className="ol-cm-toolbar-overflow">{children}</div>
  94. </OLPopover>
  95. </OLOverlay>
  96. </>
  97. )
  98. }