slideOverPanel.tsx 2.2 KB

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