section-heading-dropdown.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import classnames from 'classnames'
  2. import {
  3. useCodeMirrorStateContext,
  4. useCodeMirrorViewContext,
  5. } from '../codemirror-context'
  6. import {
  7. findCurrentSectionHeadingLevel,
  8. setSectionHeadingLevel,
  9. } from '../../extensions/toolbar/sections'
  10. import { useCallback, useMemo, useRef } from 'react'
  11. import OLOverlay from '@/shared/components/ol/ol-overlay'
  12. import OLPopover from '@/shared/components/ol/ol-popover'
  13. import useEventListener from '../../../../shared/hooks/use-event-listener'
  14. import useDropdown from '../../../../shared/hooks/use-dropdown'
  15. import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
  16. import MaterialIcon from '@/shared/components/material-icon'
  17. import { useTranslation } from 'react-i18next'
  18. import OLTooltip from '@/shared/components/ol/ol-tooltip'
  19. const levels = new Map([
  20. ['text', 'Normal text'],
  21. ['section', 'Section'],
  22. ['subsection', 'Subsection'],
  23. ['subsubsection', 'Subsubsection'],
  24. ['paragraph', 'Paragraph'],
  25. ['subparagraph', 'Subparagraph'],
  26. ])
  27. const levelsEntries = [...levels.entries()]
  28. export const SectionHeadingDropdown = () => {
  29. const state = useCodeMirrorStateContext()
  30. const view = useCodeMirrorViewContext()
  31. const { t } = useTranslation()
  32. const { open: overflowOpen, onToggle: setOverflowOpen } = useDropdown()
  33. useEventListener(
  34. 'resize',
  35. useCallback(() => {
  36. setOverflowOpen(false)
  37. }, [setOverflowOpen])
  38. )
  39. const toggleButtonRef = useRef<HTMLButtonElement | null>(null)
  40. const currentLevel = useMemo(
  41. () => findCurrentSectionHeadingLevel(state),
  42. [state]
  43. )
  44. const currentLabel = currentLevel
  45. ? (levels.get(currentLevel.level) ?? currentLevel.level)
  46. : '---'
  47. return (
  48. <>
  49. <OLTooltip
  50. id="section-heading-dropdown-tooltip"
  51. description={t('toolbar_select_style')}
  52. overlayProps={{ placement: 'bottom' }}
  53. >
  54. <button
  55. ref={toggleButtonRef}
  56. type="button"
  57. id="section-heading-menu-button"
  58. aria-haspopup="true"
  59. aria-controls="section-heading-menu"
  60. aria-label={t('toolbar_choose_section_heading_level')}
  61. className="ol-cm-toolbar-menu-toggle"
  62. onMouseDown={event => event.preventDefault()}
  63. onClick={() => setOverflowOpen(!overflowOpen)}
  64. >
  65. <span>{currentLabel}</span>
  66. <MaterialIcon type="expand_more" />
  67. </button>
  68. </OLTooltip>
  69. {overflowOpen && (
  70. <OLOverlay
  71. show
  72. onHide={() => setOverflowOpen(false)}
  73. transition={false}
  74. container={view.dom}
  75. containerPadding={0}
  76. placement="bottom"
  77. rootClose
  78. target={toggleButtonRef.current}
  79. popperConfig={{
  80. modifiers: [
  81. {
  82. name: 'offset',
  83. options: {
  84. offset: [0, 1],
  85. },
  86. },
  87. ],
  88. }}
  89. >
  90. <OLPopover
  91. id="popover-toolbar-section-heading"
  92. className="ol-cm-toolbar-menu-popover"
  93. >
  94. <div
  95. className="ol-cm-toolbar-menu"
  96. id="section-heading-menu"
  97. role="menu"
  98. aria-labelledby="section-heading-menu-button"
  99. >
  100. {levelsEntries.map(([level, label]) => (
  101. <button
  102. type="button"
  103. role="menuitem"
  104. key={level}
  105. onClick={() => {
  106. emitToolbarEvent(view, 'section-level-change')
  107. setSectionHeadingLevel(view, level)
  108. view.focus()
  109. setOverflowOpen(false)
  110. }}
  111. className={classnames(
  112. 'ol-cm-toolbar-menu-item',
  113. `section-level-${level}`,
  114. {
  115. 'ol-cm-toolbar-menu-item-active':
  116. level === currentLevel?.level,
  117. }
  118. )}
  119. >
  120. {label}
  121. </button>
  122. ))}
  123. </div>
  124. </OLPopover>
  125. </OLOverlay>
  126. )}
  127. </>
  128. )
  129. }