slideOverPanel.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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 {motion} from 'framer-motion';
  7. import {space} from 'sentry/styles/space';
  8. const PANEL_WIDTH = '50vw';
  9. const PANEL_HEIGHT = '50vh';
  10. const INITIAL_STYLES = {
  11. bottom: {opacity: 0, x: 0, y: 0},
  12. right: {opacity: 0, x: PANEL_WIDTH, y: 0},
  13. };
  14. const FINAL_STYLES = {
  15. bottom: {opacity: 0, x: 0, y: PANEL_HEIGHT},
  16. right: {opacity: 0, x: PANEL_WIDTH},
  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 initial = slidePosition ? INITIAL_STYLES[slidePosition] : INITIAL_STYLES.right;
  35. const final = slidePosition ? FINAL_STYLES[slidePosition] : FINAL_STYLES.right;
  36. return (
  37. <_SlideOverPanel
  38. ref={ref}
  39. collapsed={collapsed}
  40. initial={initial}
  41. animate={!collapsed ? {opacity: 1, x: 0, y: 0} : final}
  42. slidePosition={slidePosition}
  43. transition={{
  44. type: 'spring',
  45. stiffness: 500,
  46. damping: 50,
  47. }}
  48. >
  49. {children}
  50. </_SlideOverPanel>
  51. );
  52. }
  53. const _SlideOverPanel = styled(motion.div, {
  54. shouldForwardProp: prop =>
  55. ['animate', 'transition', 'initial'].includes(prop) ||
  56. (prop !== 'collapsed' && isPropValid(prop)),
  57. })<{
  58. collapsed: boolean;
  59. slidePosition?: 'right' | 'bottom';
  60. }>`
  61. position: fixed;
  62. top: ${space(2)};
  63. right: 0;
  64. bottom: ${space(2)};
  65. left: ${space(2)};
  66. overflow: auto;
  67. z-index: ${p => p.theme.zIndex.modal + 1};
  68. box-shadow: ${p => p.theme.dropShadowHeavy};
  69. background: ${p => p.theme.background};
  70. color: ${p => p.theme.textColor};
  71. text-align: left;
  72. @media (min-width: ${p => p.theme.breakpoints.small}) {
  73. ${p =>
  74. p.slidePosition === 'bottom'
  75. ? css`
  76. position: sticky;
  77. width: 100%;
  78. height: ${PANEL_HEIGHT};
  79. right: 0;
  80. bottom: 0;
  81. left: 0;
  82. `
  83. : css`
  84. position: fixed;
  85. width: ${PANEL_WIDTH};
  86. height: 100%;
  87. top: 0;
  88. right: 0;
  89. bottom: 0;
  90. left: auto;
  91. `}
  92. }
  93. ${p =>
  94. p.collapsed
  95. ? css`
  96. overflow: hidden;
  97. `
  98. : css`
  99. overflow-x: hidden;
  100. overflow-y: auto;
  101. `}
  102. `;