SidePanel.jsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import React from "react";
  2. import styled from "styled-components";
  3. import { useTranslation } from "react-i18next";
  4. import usePortal from "react-useportal";
  5. const Overlay = styled.div`
  6. position: fixed;
  7. left: 0;
  8. top: 0;
  9. bottom: 0;
  10. right: 0;
  11. background-color: rgba(0, 0, 0, 0.6);
  12. `;
  13. const StyledSidePanel = styled.div`
  14. position: fixed;
  15. ${({ position }) => (position === "right" ? "right: 0;" : "left: 0;")}
  16. top: 0;
  17. bottom: 0;
  18. z-index: ${({ modal, layer }) => (modal ? 290 : 280) + layer};
  19. display: flex;
  20. flex-direction: column;
  21. height: 100%;
  22. max-height: 100%;
  23. overflow: hidden;
  24. background-color: ${({ modal }) =>
  25. modal ? "var(--color-darkGrey)" : "var(--color-blueGrey)"};
  26. box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
  27. min-width: 280px;
  28. max-width: 500px;
  29. width: ${({ width }) => (width ? `${width}` : "25%")};
  30. overflow-y: auto;
  31. transform: translateX(100%);
  32. transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
  33. ${({ open, position }) => {
  34. let start = -100;
  35. let end = 0;
  36. if (position === "right") {
  37. start = 100;
  38. }
  39. return open
  40. ? `transform: translateX(${end}%);`
  41. : `transform: translateX(${start}%);`;
  42. }}
  43. ${({ open }) => (open ? "opacity: 1;" : "opacity: 0.2;")}
  44. & .close {
  45. position: fixed;
  46. top: 5px;
  47. right: 10px;
  48. }
  49. & .title {
  50. font-weight: 700;
  51. padding: 0.5em;
  52. margin: 0;
  53. }
  54. & > .content {
  55. flex: 1;
  56. overflow: auto;
  57. ${({ noMargin }) => (noMargin ? "" : "padding: 1em")};
  58. & header {
  59. padding: 0.5em;
  60. margin-top: 2em;
  61. background-color: var(--color-blueGrey);
  62. border-radius: 0.5em 0.5em 0em 0em;
  63. & h3 {
  64. margin: 0.2em 0.2em 0.2em;
  65. font-weight: 300;
  66. }
  67. }
  68. & header:first-child {
  69. margin-top: 0;
  70. }
  71. & section {
  72. border-radius: 0em 0em 0.5em 0.5em;
  73. padding: 1em;
  74. background-color: var(--color-darkBlueGrey);
  75. }
  76. }
  77. & footer {
  78. margin-top: 1em;
  79. }
  80. `;
  81. const SidePanel = ({
  82. children,
  83. position,
  84. noMargin,
  85. onClose = () => {},
  86. title,
  87. footer,
  88. show,
  89. open = show,
  90. modal = false,
  91. width,
  92. layer = 0,
  93. }) => {
  94. const { t } = useTranslation();
  95. const [bindTo] = React.useState(() =>
  96. document.getElementById(modal ? "modal-container" : "panel-container")
  97. );
  98. const { ref, Portal, openPortal, closePortal, isOpen } = usePortal({
  99. closeOnOutsideClick: modal,
  100. bindTo,
  101. });
  102. const onAnimationEnd = React.useCallback(() => {
  103. if (!isOpen) {
  104. onClose();
  105. }
  106. }, [isOpen, onClose]);
  107. React.useEffect(() => {
  108. if (open) {
  109. openPortal();
  110. } else {
  111. closePortal();
  112. }
  113. }, [openPortal, closePortal, open]);
  114. React.useEffect(() => {
  115. if (isOpen && modal) {
  116. document.getElementById("root").classList.add("blurry");
  117. } else {
  118. document.getElementById("root").classList.remove("blurry");
  119. }
  120. }, [isOpen, modal]);
  121. return (
  122. <Portal>
  123. {modal && isOpen && <Overlay onClick={closePortal} />}
  124. <StyledSidePanel
  125. position={position}
  126. open={isOpen}
  127. onTransitionEnd={onAnimationEnd}
  128. noMargin={noMargin}
  129. ref={ref}
  130. width={width}
  131. modal={modal}
  132. className={isOpen ? "side-panel open" : "side-panel"}
  133. layer={layer}
  134. >
  135. <header>
  136. {title && <h2 className="title">{title}</h2>}
  137. <button
  138. className="button clear icon-only close"
  139. onClick={closePortal}
  140. >
  141. <img
  142. src="https://icongr.am/feather/x.svg?size=42&color=ffffff"
  143. alt={t("Close")}
  144. />
  145. </button>
  146. </header>
  147. <div className="content">{open && children}</div>
  148. {footer && <footer>{footer}</footer>}
  149. </StyledSidePanel>
  150. </Portal>
  151. );
  152. };
  153. export default SidePanel;