diff options
Diffstat (limited to 'gui/src/renderer/components/Modal.tsx')
| -rw-r--r-- | gui/src/renderer/components/Modal.tsx | 98 |
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, { |
