slideOverPanel.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import type {ForwardedRef} from 'react';
  2. import {forwardRef, useEffect} from 'react';
  3. import isPropValid from '@emotion/is-prop-valid';
  4. import {css} from '@emotion/react';
  5. import styled from '@emotion/styled';
  6. import {AnimatePresence, motion} from 'framer-motion';
  7. import {space} from 'sentry/styles/space';
  8. const PANEL_WIDTH = '50vw';
  9. const PANEL_HEIGHT = '50vh';
  10. const OPEN_STYLES = {
  11. bottom: {opacity: 1, x: 0, y: 0},
  12. right: {opacity: 1, x: 0, y: 0},
  13. };
  14. const COLLAPSED_STYLES = {
  15. bottom: {opacity: 0, x: 0, y: PANEL_HEIGHT},
  16. right: {opacity: 0, x: PANEL_WIDTH, y: 0},
  17. };
  18. type SlideOverPanelProps = {
  19. children: React.ReactNode;
  20. collapsed: boolean;
  21. onOpen?: () => void;
  22. slidePosition?: 'right' | 'bottom';
  23. };
  24. export default forwardRef(SlideOverPanel);
  25. function SlideOverPanel(
  26. {collapsed, children, onOpen, slidePosition}: SlideOverPanelProps,
  27. ref: ForwardedRef<HTMLDivElement>
  28. ) {
  29. useEffect(() => {
  30. if (!collapsed && onOpen) {
  31. onOpen();
  32. }
  33. }, [collapsed, onOpen]);
  34. const openStyle = slidePosition ? OPEN_STYLES[slidePosition] : OPEN_STYLES.right;
  35. const collapsedStyle = slidePosition
  36. ? COLLAPSED_STYLES[slidePosition]
  37. : COLLAPSED_STYLES.right;
  38. return (
  39. <AnimatePresence>
  40. {!collapsed && (
  41. <_SlideOverPanel
  42. ref={ref}
  43. initial={collapsedStyle}
  44. animate={openStyle}
  45. exit={collapsedStyle}
  46. slidePosition={slidePosition}
  47. transition={{
  48. type: 'spring',
  49. stiffness: 500,
  50. damping: 50,
  51. }}
  52. >
  53. {children}
  54. </_SlideOverPanel>
  55. )}
  56. </AnimatePresence>
  57. );
  58. }
  59. const _SlideOverPanel = styled(motion.div, {
  60. shouldForwardProp: prop =>
  61. ['initial', 'animate', 'exit', 'transition'].includes(prop) ||
  62. (prop !== 'collapsed' && isPropValid(prop)),
  63. })<{
  64. slidePosition?: 'right' | 'bottom';
  65. }>`
  66. position: fixed;
  67. top: ${space(2)};
  68. right: 0;
  69. bottom: ${space(2)};
  70. left: ${space(2)};
  71. overflow: auto;
  72. z-index: ${p => p.theme.zIndex.modal + 1};
  73. box-shadow: ${p => p.theme.dropShadowHeavy};
  74. background: ${p => p.theme.background};
  75. color: ${p => p.theme.textColor};
  76. text-align: left;
  77. @media (min-width: ${p => p.theme.breakpoints.small}) {
  78. ${p =>
  79. p.slidePosition === 'bottom'
  80. ? css`
  81. position: sticky;
  82. width: 100%;
  83. height: ${PANEL_HEIGHT};
  84. right: 0;
  85. bottom: 0;
  86. left: 0;
  87. `
  88. : css`
  89. position: fixed;
  90. width: ${PANEL_WIDTH};
  91. height: 100%;
  92. top: 0;
  93. right: 0;
  94. bottom: 0;
  95. left: auto;
  96. `}
  97. }
  98. `;