summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer/components/Modal.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src/renderer/components/Modal.tsx')
-rw-r--r--gui/src/renderer/components/Modal.tsx98
1 files changed, 81 insertions, 17 deletions
diff --git a/gui/src/renderer/components/Modal.tsx b/gui/src/renderer/components/Modal.tsx
index 2713144e93..6b8a9a67dc 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';
@@ -20,8 +20,9 @@ const ModalContent = styled.div({
bottom: 0,
});
-const ModalBackground = styled.div({
- backgroundColor: 'rgba(0,0,0,0.5)',
+const ModalBackground = styled.div({}, (props: { visible: boolean }) => ({
+ backgroundColor: props.visible ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0)',
+ backdropFilter: props.visible ? 'blur(1.5px)' : '',
position: 'absolute',
display: 'flex',
flexDirection: 'column',
@@ -30,7 +31,10 @@ const ModalBackground = styled.div({
left: 0,
right: 0,
bottom: 0,
-});
+ transition: 'all 150ms ease-out',
+ pointerEvents: props.visible ? 'auto' : 'none',
+ zIndex: 2,
+}));
export const StyledModalContainer = styled.div({
position: 'relative',
@@ -102,13 +106,26 @@ const ModalAlertContainer = styled.div({
padding: '26px 14px 14px',
});
-const StyledModalAlert = styled.div({
- display: 'flex',
- flexDirection: 'column',
- backgroundColor: colors.darkBlue,
- borderRadius: '11px',
- padding: '16px 0 16px 16px',
- maxHeight: '80vh',
+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)({
@@ -137,16 +154,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;
+}
+
+interface IModalAlertImplProps extends IModalAlertProps, IModalContext {
+ closing: boolean;
+ onTransitionEnd: () => void;
}
-class ModalAlertWithContext extends React.Component<IModalAlertProps & IModalContext> {
+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) {
@@ -164,6 +213,8 @@ class ModalAlertWithContext extends React.Component<IModalAlertProps & IModalCon
if (modalContainer) {
modalContainer.appendChild(this.element);
this.modalRef.current?.focus();
+
+ this.setState({ visible: true });
} else {
log.error('Modal container not found when mounting modal');
}
@@ -183,9 +234,16 @@ class ModalAlertWithContext extends React.Component<IModalAlertProps & IModalCon
private renderModal() {
return (
- <ModalBackground>
+ <ModalBackground visible={this.state.visible && !this.props.closing}>
<ModalAlertContainer>
- <StyledModalAlert ref={this.modalRef} tabIndex={-1} role="dialog" aria-modal>
+ <StyledModalAlert
+ ref={this.modalRef}
+ tabIndex={-1}
+ role="dialog"
+ aria-modal
+ visible={this.state.visible}
+ closing={this.props.closing}
+ onTransitionEnd={this.onTransitionEnd}>
<StyledCustomScrollbars>
{this.props.type && (
<ModalAlertIcon>{this.renderTypeIcon(this.props.type)}</ModalAlertIcon>
@@ -231,6 +289,12 @@ class ModalAlertWithContext extends React.Component<IModalAlertProps & IModalCon
this.props.close?.();
}
};
+
+ private onTransitionEnd = (event: React.TransitionEvent<HTMLDivElement>) => {
+ if (event.target === this.modalRef.current) {
+ this.props.onTransitionEnd();
+ }
+ };
}
export const ModalMessage = styled.span(tinyText, {