You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BaseDialog.tsx 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import React, { ReactNode, useCallback, useContext, useEffect } from 'react';
  2. import { FocusOn } from 'react-focus-on';
  3. import { useTranslation } from 'react-i18next';
  4. import { keyframes } from 'tss-react';
  5. import { makeStyles } from 'tss-react/mui';
  6. import { withPixelLineHeight } from '../../../styles/functions.web';
  7. import { isElementInTheViewport } from '../../functions.web';
  8. import { DialogTransitionContext } from './DialogTransition';
  9. const useStyles = makeStyles()(theme => {
  10. return {
  11. container: {
  12. width: '100%',
  13. height: '100%',
  14. position: 'fixed',
  15. color: theme.palette.text01,
  16. ...withPixelLineHeight(theme.typography.bodyLongRegular),
  17. top: 0,
  18. left: 0,
  19. display: 'flex',
  20. justifyContent: 'center',
  21. alignItems: 'flex-start',
  22. zIndex: 301,
  23. animation: `${keyframes`
  24. 0% {
  25. opacity: 0.4;
  26. }
  27. 100% {
  28. opacity: 1;
  29. }
  30. `} 0.2s forwards ease-out`,
  31. '&.unmount': {
  32. animation: `${keyframes`
  33. 0% {
  34. opacity: 1;
  35. }
  36. 100% {
  37. opacity: 0.5;
  38. }
  39. `} 0.15s forwards ease-in`
  40. }
  41. },
  42. backdrop: {
  43. position: 'absolute',
  44. width: '100%',
  45. height: '100%',
  46. top: 0,
  47. left: 0,
  48. backgroundColor: theme.palette.ui02,
  49. opacity: 0.75
  50. },
  51. modal: {
  52. backgroundColor: theme.palette.ui01,
  53. border: `1px solid ${theme.palette.ui03}`,
  54. boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)',
  55. borderRadius: `${theme.shape.borderRadius}px`,
  56. display: 'flex',
  57. flexDirection: 'column',
  58. height: 'auto',
  59. minHeight: '200px',
  60. maxHeight: '80vh',
  61. marginTop: '64px',
  62. animation: `${keyframes`
  63. 0% {
  64. margin-top: 85px
  65. }
  66. 100% {
  67. margin-top: 64px
  68. }
  69. `} 0.2s forwards ease-out`,
  70. '&.medium': {
  71. width: '400px'
  72. },
  73. '&.large': {
  74. width: '664px'
  75. },
  76. '&.unmount': {
  77. animation: `${keyframes`
  78. 0% {
  79. margin-top: 64px
  80. }
  81. 100% {
  82. margin-top: 40px
  83. }
  84. `} 0.15s forwards ease-in`
  85. },
  86. '@media (max-width: 448px)': {
  87. width: '100% !important',
  88. maxHeight: 'initial',
  89. height: '100%',
  90. margin: 0,
  91. position: 'absolute',
  92. top: 0,
  93. left: 0,
  94. bottom: 0,
  95. animation: `${keyframes`
  96. 0% {
  97. margin-top: 15px
  98. }
  99. 100% {
  100. margin-top: 0
  101. }
  102. `} 0.2s forwards ease-out`,
  103. '&.unmount': {
  104. animation: `${keyframes`
  105. 0% {
  106. margin-top: 0
  107. }
  108. 100% {
  109. margin-top: 15px
  110. }
  111. `} 0.15s forwards ease-in`
  112. }
  113. }
  114. },
  115. focusLock: {
  116. zIndex: 1
  117. }
  118. };
  119. });
  120. export interface IProps {
  121. children?: ReactNode;
  122. className?: string;
  123. description?: string;
  124. disableBackdropClose?: boolean;
  125. disableEnter?: boolean;
  126. disableEscape?: boolean;
  127. onClose?: () => void;
  128. size?: 'large' | 'medium';
  129. submit?: () => void;
  130. testId?: string;
  131. title?: string;
  132. titleKey?: string;
  133. }
  134. const BaseDialog = ({
  135. children,
  136. className,
  137. description,
  138. disableBackdropClose,
  139. disableEnter,
  140. disableEscape,
  141. onClose,
  142. size = 'medium',
  143. submit,
  144. testId,
  145. title,
  146. titleKey
  147. }: IProps) => {
  148. const { classes, cx } = useStyles();
  149. const { isUnmounting } = useContext(DialogTransitionContext);
  150. const { t } = useTranslation();
  151. const onBackdropClick = useCallback(() => {
  152. !disableBackdropClose && onClose?.();
  153. }, [ disableBackdropClose, onClose ]);
  154. const handleKeyDown = useCallback((e: KeyboardEvent) => {
  155. if (e.key === 'Escape' && !disableEscape) {
  156. onClose?.();
  157. }
  158. if (e.key === 'Enter' && !disableEnter) {
  159. submit?.();
  160. }
  161. }, [ disableEnter, onClose, submit ]);
  162. useEffect(() => {
  163. window.addEventListener('keydown', handleKeyDown);
  164. return () => window.removeEventListener('keydown', handleKeyDown);
  165. }, [ handleKeyDown ]);
  166. return (
  167. <div
  168. className = { cx(classes.container, isUnmounting && 'unmount') }
  169. data-testid = { testId }>
  170. <div className = { classes.backdrop } />
  171. <FocusOn
  172. className = { classes.focusLock }
  173. onClickOutside = { onBackdropClick }
  174. returnFocus = {
  175. // If we return the focus to an element outside the viewport the page will scroll to
  176. // this element which in our case is undesirable and the element is outside of the
  177. // viewport on purpose (to be hidden). For example if we return the focus to the toolbox
  178. // when it is hidden the whole page will move up in order to show the toolbox. This is
  179. // usually followed up with displaying the toolbox (because now it is on focus) but
  180. // because of the animation the whole scenario looks like jumping large video.
  181. isElementInTheViewport
  182. }>
  183. <div
  184. aria-description = { description }
  185. aria-label = { title ?? t(titleKey ?? '') }
  186. aria-modal = { true }
  187. className = { cx(classes.modal, isUnmounting && 'unmount', size, className) }
  188. data-autofocus = { true }
  189. role = 'dialog'
  190. tabIndex = { -1 }>
  191. {children}
  192. </div>
  193. </FocusOn>
  194. </div>
  195. );
  196. };
  197. export default BaseDialog;