splitPanel.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import {ReactNode, useCallback} from 'react';
  2. import styled from '@emotion/styled';
  3. import debounce from 'lodash/debounce';
  4. import useSplitPanelTracking from 'sentry/utils/replays/hooks/useSplitPanelTracking';
  5. import {useResizableDrawer} from 'sentry/utils/useResizableDrawer';
  6. import SplitDivider from 'sentry/views/replays/detail/layout/splitDivider';
  7. type Side = {
  8. content: ReactNode;
  9. default: number;
  10. max: number;
  11. min: number;
  12. };
  13. type Props =
  14. | {
  15. availableSize: number;
  16. /**
  17. * Content on the right side of the split
  18. */
  19. left: Side;
  20. /**
  21. * Content on the left side of the split
  22. */
  23. right: ReactNode;
  24. }
  25. | {
  26. availableSize: number;
  27. /**
  28. * Content below the split
  29. */
  30. bottom: ReactNode;
  31. /**
  32. * Content above of the split
  33. */
  34. top: Side;
  35. };
  36. function SplitPanel(props: Props) {
  37. const isLeftRight = 'left' in props;
  38. const initialSize = isLeftRight ? props.left.default : props.top.default;
  39. const min = isLeftRight ? props.left.min : props.top.min;
  40. const max = isLeftRight ? props.left.max : props.top.max;
  41. const {setStartPosition, logEndPosition} = useSplitPanelTracking({
  42. slideDirection: isLeftRight ? 'leftright' : 'updown',
  43. });
  44. // eslint-disable-next-line react-hooks/exhaustive-deps
  45. const onResize = useCallback(
  46. debounce(newSize => logEndPosition(`${(newSize / props.availableSize) * 100}%`), 750),
  47. [debounce, logEndPosition, props.availableSize]
  48. );
  49. const {
  50. isHeld,
  51. onDoubleClick,
  52. onMouseDown: onDragStart,
  53. size: containerSize,
  54. } = useResizableDrawer({
  55. direction: isLeftRight ? 'left' : 'down',
  56. initialSize,
  57. min,
  58. onResize,
  59. });
  60. const sizePct = `${
  61. (Math.min(containerSize, max) / props.availableSize) * 100
  62. }%` as `${number}%`;
  63. const handleMouseDown = useCallback(
  64. event => {
  65. setStartPosition(sizePct);
  66. onDragStart(event);
  67. },
  68. [setStartPosition, onDragStart, sizePct]
  69. );
  70. if (isLeftRight) {
  71. const {left: a, right: b} = props;
  72. return (
  73. <SplitPanelContainer
  74. className={isHeld ? 'disable-iframe-pointer' : undefined}
  75. orientation="columns"
  76. size={sizePct}
  77. >
  78. <Panel>{a.content}</Panel>
  79. <SplitDivider
  80. data-is-held={isHeld}
  81. data-slide-direction="leftright"
  82. onDoubleClick={onDoubleClick}
  83. onMouseDown={handleMouseDown}
  84. />
  85. <Panel>{b}</Panel>
  86. </SplitPanelContainer>
  87. );
  88. }
  89. const {top: a, bottom: b} = props;
  90. return (
  91. <SplitPanelContainer
  92. orientation="rows"
  93. size={sizePct}
  94. className={isHeld ? 'disable-iframe-pointer' : undefined}
  95. >
  96. <Panel>{a.content}</Panel>
  97. <SplitDivider
  98. data-is-held={isHeld}
  99. data-slide-direction="updown"
  100. onDoubleClick={onDoubleClick}
  101. onMouseDown={handleMouseDown}
  102. />
  103. <Panel>{b}</Panel>
  104. </SplitPanelContainer>
  105. );
  106. }
  107. const SplitPanelContainer = styled('div')<{
  108. orientation: 'rows' | 'columns';
  109. size: `${number}px` | `${number}%`;
  110. }>`
  111. width: 100%;
  112. height: 100%;
  113. position: relative;
  114. display: grid;
  115. overflow: auto;
  116. grid-template-${p => p.orientation}: ${p => p.size} auto 1fr;
  117. &.disable-iframe-pointer iframe {
  118. pointer-events: none !important;
  119. }
  120. `;
  121. const Panel = styled('div')`
  122. overflow: hidden;
  123. `;
  124. export default SplitPanel;