summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-02-17 10:37:38 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-02-18 11:30:54 +0100
commitd7add7d703fcbc7d513a396ad678d99d0a5b96cf (patch)
tree8bcbc86635d21b9e1db40595c03dea2d033e4589 /gui/src
parentb2d936637596901004ae7eefabdde56dec9e8bee (diff)
downloadmullvadvpn-d7add7d703fcbc7d513a396ad678d99d0a5b96cf.tar.xz
mullvadvpn-d7add7d703fcbc7d513a396ad678d99d0a5b96cf.zip
Add modal hide transition
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/Modal.tsx85
1 files changed, 63 insertions, 22 deletions
diff --git a/gui/src/renderer/components/Modal.tsx b/gui/src/renderer/components/Modal.tsx
index 841211beb4..f0c1008f6b 100644
--- a/gui/src/renderer/components/Modal.tsx
+++ b/gui/src/renderer/components/Modal.tsx
@@ -1,4 +1,4 @@
-import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
+import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import styled from 'styled-components';
import { colors } from '../../config.json';
@@ -32,6 +32,7 @@ const ModalBackground = styled.div({}, (props: { visible: boolean }) => ({
right: 0,
bottom: 0,
transition: 'all 150ms ease-out',
+ pointerEvents: props.visible ? 'auto' : 'none',
}));
export const StyledModalContainer = styled.div({
@@ -104,18 +105,27 @@ const ModalAlertContainer = styled.div({
padding: '26px 14px 14px',
});
-const StyledModalAlert = styled.div({}, (props: { visible: boolean }) => ({
- display: 'flex',
- flexDirection: 'column',
- backgroundColor: colors.darkBlue,
- borderRadius: '11px',
- padding: '16px 0 16px 16px',
- maxHeight: '80vh',
- opacity: props.visible ? 1 : 0,
- transform: props.visible ? '' : 'translateY(10px) scale(98%)',
- boxShadow: ' 0px 15px 35px 5px rgba(0,0,0,0.5)',
- transition: 'all 150ms ease-out',
-}));
+const StyledModalAlert = styled.div({}, (props: { visible: boolean; closing: boolean }) => {
+ let transform = '';
+ if (props.visible && props.closing) {
+ transform = 'scale(80%)';
+ } else if (!props.visible) {
+ transform = 'translateY(10px) scale(98%)';
+ }
+
+ return {
+ display: 'flex',
+ flexDirection: 'column',
+ backgroundColor: colors.darkBlue,
+ borderRadius: '11px',
+ padding: '16px 0 16px 16px',
+ maxHeight: '80vh',
+ opacity: props.visible && !props.closing ? 1 : 0,
+ transform,
+ boxShadow: ' 0px 15px 35px 5px rgba(0,0,0,0.5)',
+ transition: 'all 150ms ease-out',
+ };
+});
const StyledCustomScrollbars = styled(CustomScrollbars)({
paddingRight: '16px',
@@ -143,25 +153,48 @@ interface IModalAlertProps {
close?: () => void;
}
-export function ModalAlert(props: IModalAlertProps) {
+export function ModalAlert(props: IModalAlertProps & { isOpen: boolean }) {
+ const { isOpen, ...otherProps } = props;
const activeModalContext = useContext(ActiveModalContext);
- return <ModalAlertWithContext {...activeModalContext} {...props} />;
+ const [closing, setClosing] = useState(false);
+ const prevIsOpen = useRef(isOpen);
+
+ const onTransitionEnd = useCallback(() => setClosing(false), []);
+ useEffect(() => {
+ setClosing((closing) => closing || (prevIsOpen.current && !isOpen));
+ prevIsOpen.current = isOpen;
+ }, [isOpen]);
+
+ if (!prevIsOpen.current && !isOpen && !closing) {
+ return null;
+ }
+
+ return (
+ <ModalAlertImpl
+ {...activeModalContext}
+ {...otherProps}
+ closing={closing}
+ onTransitionEnd={onTransitionEnd}
+ />
+ );
}
interface IModalAlertState {
visible: boolean;
}
-class ModalAlertWithContext extends React.Component<
- IModalAlertProps & IModalContext,
- IModalAlertState
-> {
+interface IModalAlertImplProps extends IModalAlertProps, IModalContext {
+ closing: boolean;
+ onTransitionEnd: () => void;
+}
+
+class ModalAlertImpl extends React.Component<IModalAlertImplProps, IModalAlertState> {
public state = { visible: false };
private element = document.createElement('div');
private modalRef = React.createRef<HTMLDivElement>();
- constructor(props: IModalAlertProps & IModalContext) {
+ constructor(props: IModalAlertImplProps) {
super(props);
if (document.activeElement) {
@@ -200,14 +233,16 @@ class ModalAlertWithContext extends React.Component<
private renderModal() {
return (
- <ModalBackground visible={this.state.visible}>
+ <ModalBackground visible={this.state.visible && !this.props.closing}>
<ModalAlertContainer>
<StyledModalAlert
ref={this.modalRef}
tabIndex={-1}
role="dialog"
aria-modal
- visible={this.state.visible}>
+ visible={this.state.visible}
+ closing={this.props.closing}
+ onTransitionEnd={this.onTransitionEnd}>
<StyledCustomScrollbars>
{this.props.type && (
<ModalAlertIcon>{this.renderTypeIcon(this.props.type)}</ModalAlertIcon>
@@ -253,6 +288,12 @@ class ModalAlertWithContext extends React.Component<
this.props.close?.();
}
};
+
+ private onTransitionEnd = (event: React.TransitionEvent<HTMLDivElement>) => {
+ if (event.target === this.modalRef.current) {
+ this.props.onTransitionEnd();
+ }
+ };
}
export const ModalMessage = styled.span(tinyText, {