table-inserter-dropdown.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { FC, useCallback, useRef, useState } from 'react'
  2. import * as commands from '../../extensions/toolbar/commands'
  3. import { useTranslation } from 'react-i18next'
  4. import useDropdown from '../../../../shared/hooks/use-dropdown'
  5. import { Button, Overlay, Popover } from 'react-bootstrap'
  6. import { useCodeMirrorViewContext } from '../codemirror-editor'
  7. import Tooltip from '../../../../shared/components/tooltip'
  8. import MaterialIcon from '../../../../shared/components/material-icon'
  9. import classNames from 'classnames'
  10. import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
  11. export const TableInserterDropdown: FC = () => {
  12. const { t } = useTranslation()
  13. const { open, onToggle, ref } = useDropdown()
  14. const view = useCodeMirrorViewContext()
  15. const target = useRef<any>(null)
  16. const onSizeSelected = useCallback(
  17. (sizeX: number, sizeY: number) => {
  18. onToggle(false)
  19. commands.insertTable(view, sizeX, sizeY)
  20. emitToolbarEvent(view, 'table-generator-insert-table')
  21. view.focus()
  22. },
  23. [view, onToggle]
  24. )
  25. return (
  26. <>
  27. <Tooltip
  28. hidden={open}
  29. id="toolbar-table"
  30. description={<div>{t('toolbar_insert_table')}</div>}
  31. overlayProps={{ placement: 'bottom' }}
  32. >
  33. <Button
  34. type="button"
  35. className="ol-cm-toolbar-button"
  36. aria-label={t('toolbar_insert_table')}
  37. bsStyle={null}
  38. onMouseDown={event => {
  39. event.preventDefault()
  40. event.stopPropagation()
  41. }}
  42. onClick={() => {
  43. onToggle(!open)
  44. }}
  45. ref={target}
  46. >
  47. <MaterialIcon type="table_chart" />
  48. </Button>
  49. </Tooltip>
  50. <Overlay
  51. show={open}
  52. target={target.current}
  53. placement="bottom"
  54. container={view.dom}
  55. containerPadding={0}
  56. animation
  57. onHide={() => onToggle(false)}
  58. >
  59. <Popover
  60. id="toolbar-table-menu"
  61. ref={ref}
  62. className="ol-cm-toolbar-button-menu-popover ol-cm-toolbar-table-grid-popover"
  63. >
  64. <SizeGrid sizeX={10} sizeY={10} onSizeSelected={onSizeSelected} />
  65. </Popover>
  66. </Overlay>
  67. </>
  68. )
  69. }
  70. const range = (start: number, end: number) =>
  71. Array.from({ length: end - start + 1 }, (v, k) => k + start)
  72. const SizeGrid: FC<{
  73. sizeX: number
  74. sizeY: number
  75. onSizeSelected: (sizeX: number, sizeY: number) => void
  76. }> = ({ sizeX, sizeY, onSizeSelected }) => {
  77. const [currentSize, setCurrentSize] = useState<{
  78. sizeX: number
  79. sizeY: number
  80. }>({ sizeX: 0, sizeY: 0 })
  81. const { t } = useTranslation()
  82. let label = t('toolbar_table_insert_table_lowercase')
  83. if (currentSize.sizeX > 0 && currentSize.sizeY > 0) {
  84. label = t('toolbar_table_insert_size_table', {
  85. size: `${currentSize.sizeY}×${currentSize.sizeX}`,
  86. })
  87. }
  88. return (
  89. <>
  90. <div className="ol-cm-toolbar-table-size-label">{label}</div>
  91. <table
  92. className="ol-cm-toolbar-table-grid"
  93. onMouseLeave={() => {
  94. setCurrentSize({ sizeX: 0, sizeY: 0 })
  95. }}
  96. >
  97. <tbody>
  98. {range(1, sizeY).map(y => (
  99. <tr key={y}>
  100. {range(1, sizeX).map(x => (
  101. // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
  102. <td
  103. className={classNames('ol-cm-toolbar-table-cell', {
  104. active: currentSize.sizeX >= x && currentSize.sizeY >= y,
  105. })}
  106. key={x}
  107. onMouseEnter={() => {
  108. setCurrentSize({ sizeX: x, sizeY: y })
  109. }}
  110. onMouseUp={() => onSizeSelected(x, y)}
  111. />
  112. ))}
  113. </tr>
  114. ))}
  115. </tbody>
  116. </table>
  117. </>
  118. )
  119. }