summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer/components
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-02-18 11:31:08 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-02-18 11:31:08 +0100
commit89fe34efa3e3d5287cacd7bbd9f2289707f66dfe (patch)
treeaacbb9802149ec13473f20b9100bd2d17d549fa2 /gui/src/renderer/components
parentb23259c3b6000ff3b9ba0a3b359216098759fcfa (diff)
parent4c5c74e844d5e4637ef4a8ce129d98b55f2314f6 (diff)
downloadmullvadvpn-89fe34efa3e3d5287cacd7bbd9f2289707f66dfe.tar.xz
mullvadvpn-89fe34efa3e3d5287cacd7bbd9f2289707f66dfe.zip
Merge branch 'improve-dialog-styles'
Diffstat (limited to 'gui/src/renderer/components')
-rw-r--r--gui/src/renderer/components/AdvancedSettings.tsx4
-rw-r--r--gui/src/renderer/components/Changelog.tsx14
-rw-r--r--gui/src/renderer/components/CustomDnsSettings.tsx8
-rw-r--r--gui/src/renderer/components/ExpiredAccountErrorView.tsx3
-rw-r--r--gui/src/renderer/components/Modal.tsx98
-rw-r--r--gui/src/renderer/components/OpenVPNSettings.tsx3
-rw-r--r--gui/src/renderer/components/Preferences.tsx29
-rw-r--r--gui/src/renderer/components/RedeemVoucher.tsx11
-rw-r--r--gui/src/renderer/components/SplitTunnelingSettings.tsx56
-rw-r--r--gui/src/renderer/components/Support.tsx15
-rw-r--r--gui/src/renderer/components/WireguardSettings.tsx3
11 files changed, 160 insertions, 84 deletions
diff --git a/gui/src/renderer/components/AdvancedSettings.tsx b/gui/src/renderer/components/AdvancedSettings.tsx
index 7d873ffca5..d811f04a06 100644
--- a/gui/src/renderer/components/AdvancedSettings.tsx
+++ b/gui/src/renderer/components/AdvancedSettings.tsx
@@ -187,8 +187,7 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
</NavigationContainer>
</SettingsContainer>
- {this.state.showConfirmBlockWhenDisconnectedAlert &&
- this.renderConfirmBlockWhenDisconnectedAlert()}
+ {this.renderConfirmBlockWhenDisconnectedAlert()}
</Layout>
);
}
@@ -221,6 +220,7 @@ export default class AdvancedSettings extends React.Component<IProps, IState> {
private renderConfirmBlockWhenDisconnectedAlert = () => {
return (
<ModalAlert
+ isOpen={this.state.showConfirmBlockWhenDisconnectedAlert}
type={ModalAlertType.caution}
buttons={[
<AppButton.RedButton key="confirm" onClick={this.confirmEnableBlockWhenDisconnected}>
diff --git a/gui/src/renderer/components/Changelog.tsx b/gui/src/renderer/components/Changelog.tsx
index 545ed6753b..039fe9bbc4 100644
--- a/gui/src/renderer/components/Changelog.tsx
+++ b/gui/src/renderer/components/Changelog.tsx
@@ -37,17 +37,15 @@ export function Changelog() {
const { setDisplayedChangelog } = useAppContext();
- if (
- changelogDisplayedForVersion === currentVersion ||
- changelog.length === 0 ||
- window.env.development ||
- /-dev-[0-9a-f]{6}$/.test(currentVersion)
- ) {
- return null;
- }
+ const visible =
+ changelogDisplayedForVersion !== currentVersion &&
+ changelog.length > 0 &&
+ !window.env.development &&
+ !/-dev-[0-9a-f]{6}$/.test(currentVersion);
return (
<ModalAlert
+ isOpen={visible}
buttons={[
<AppButton.BlueButton key="close" onClick={setDisplayedChangelog}>
{
diff --git a/gui/src/renderer/components/CustomDnsSettings.tsx b/gui/src/renderer/components/CustomDnsSettings.tsx
index bbecd4b3f2..287ed58a4b 100644
--- a/gui/src/renderer/components/CustomDnsSettings.tsx
+++ b/gui/src/renderer/components/CustomDnsSettings.tsx
@@ -270,7 +270,11 @@ export default function CustomDnsSettings() {
</Cell.FooterText>
</StyledCustomDnsFooter>
- {confirmAction && <ConfirmationDialog confirm={confirm} abort={abortConfirmation} />}
+ <ConfirmationDialog
+ isOpen={confirmAction !== undefined}
+ confirm={confirm}
+ abort={abortConfirmation}
+ />
</>
);
}
@@ -391,6 +395,7 @@ function CellListItem(props: ICellListItemProps) {
}
interface IConfirmationDialogProps {
+ isOpen: boolean;
confirm: () => void;
abort: () => void;
}
@@ -398,6 +403,7 @@ interface IConfirmationDialogProps {
function ConfirmationDialog(props: IConfirmationDialogProps) {
return (
<ModalAlert
+ isOpen={props.isOpen}
type={ModalAlertType.caution}
buttons={[
<AppButton.RedButton key="confirm" onClick={props.confirm}>
diff --git a/gui/src/renderer/components/ExpiredAccountErrorView.tsx b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
index 6f29b3dad2..8b2439e406 100644
--- a/gui/src/renderer/components/ExpiredAccountErrorView.tsx
+++ b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
@@ -87,7 +87,7 @@ export default class ExpiredAccountErrorView extends React.Component<
</AppButton.GreenButton>
</StyledFooter>
- {this.state.showBlockWhenDisconnectedAlert && this.renderBlockWhenDisconnectedAlert()}
+ {this.renderBlockWhenDisconnectedAlert()}
</StyledContainer>
</StyledCustomScrollbars>
</Layout>
@@ -192,6 +192,7 @@ export default class ExpiredAccountErrorView extends React.Component<
private renderBlockWhenDisconnectedAlert() {
return (
<ModalAlert
+ isOpen={this.state.showBlockWhenDisconnectedAlert}
type={ModalAlertType.caution}
buttons={[
<AppButton.BlueButton
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, {
diff --git a/gui/src/renderer/components/OpenVPNSettings.tsx b/gui/src/renderer/components/OpenVPNSettings.tsx
index b554eb2372..55157a9bfe 100644
--- a/gui/src/renderer/components/OpenVPNSettings.tsx
+++ b/gui/src/renderer/components/OpenVPNSettings.tsx
@@ -224,7 +224,7 @@ export default class OpenVpnSettings extends React.Component<IProps, IState> {
</NavigationContainer>
</SettingsContainer>
- {this.state.showBridgeStateConfirmationDialog && this.renderBridgeStateConfirmation()}
+ {this.renderBridgeStateConfirmation()}
</Layout>
);
}
@@ -326,6 +326,7 @@ export default class OpenVpnSettings extends React.Component<IProps, IState> {
private renderBridgeStateConfirmation = () => {
return (
<ModalAlert
+ isOpen={this.state.showBridgeStateConfirmationDialog}
type={ModalAlertType.info}
message={messages.gettext('This setting increases latency. Use only if needed.')}
buttons={[
diff --git a/gui/src/renderer/components/Preferences.tsx b/gui/src/renderer/components/Preferences.tsx
index f1c349c664..e3e4ccd270 100644
--- a/gui/src/renderer/components/Preferences.tsx
+++ b/gui/src/renderer/components/Preferences.tsx
@@ -351,21 +351,20 @@ export default class Preferences extends React.Component<IProps, IState> {
</NavigationContainer>
</StyledContainer>
- {this.state.showKillSwitchInfo && (
- <ModalAlert
- message={messages.pgettext(
- 'preferences-view',
- 'The app has a built in kill switch that is enabled by default and cannot be disabled. This is to prevent your traffic from leaking outside of the VPN tunnel if your network suddenly stops working or if the tunnel fails for any reason. Mullvad automatically protects your data until your connection is reestablished.',
- )}
- type={ModalAlertType.info}
- buttons={[
- <AppButton.BlueButton key="back" onClick={this.hideKillSwitchInfo}>
- {messages.gettext('Got it!')}
- </AppButton.BlueButton>,
- ]}
- close={this.hideKillSwitchInfo}
- />
- )}
+ <ModalAlert
+ isOpen={this.state.showKillSwitchInfo}
+ message={messages.pgettext(
+ 'preferences-view',
+ 'The app has a built in kill switch that is enabled by default and cannot be disabled. This is to prevent your traffic from leaking outside of the VPN tunnel if your network suddenly stops working or if the tunnel fails for any reason. Mullvad automatically protects your data until your connection is reestablished.',
+ )}
+ type={ModalAlertType.info}
+ buttons={[
+ <AppButton.BlueButton key="back" onClick={this.hideKillSwitchInfo}>
+ {messages.gettext('Got it!')}
+ </AppButton.BlueButton>,
+ ]}
+ close={this.hideKillSwitchInfo}
+ />
</Layout>
);
}
diff --git a/gui/src/renderer/components/RedeemVoucher.tsx b/gui/src/renderer/components/RedeemVoucher.tsx
index 7810112082..ef14ef77a9 100644
--- a/gui/src/renderer/components/RedeemVoucher.tsx
+++ b/gui/src/renderer/components/RedeemVoucher.tsx
@@ -207,6 +207,7 @@ export function RedeemVoucherSubmitButton() {
}
interface IRedeemVoucherAlertProps {
+ show: boolean;
onClose?: () => void;
}
@@ -220,6 +221,7 @@ export function RedeemVoucherAlert(props: IRedeemVoucherAlertProps) {
return (
<ModalAlert
+ isOpen={props.show}
buttons={[
<AppButton.BlueButton key="gotit" onClick={props.onClose}>
{messages.gettext('Got it!')}
@@ -243,6 +245,7 @@ export function RedeemVoucherAlert(props: IRedeemVoucherAlertProps) {
} else {
return (
<ModalAlert
+ isOpen={props.show}
buttons={[
<RedeemVoucherSubmitButton key="submit" />,
<AppButton.BlueButton key="cancel" disabled={submitting} onClick={props.onClose}>
@@ -273,11 +276,9 @@ export function RedeemVoucherButton(props: IRedeemVoucherButtonProps) {
<AppButton.GreenButton onClick={onClick} className={props.className}>
{messages.pgettext('redeem-voucher-alert', 'Redeem voucher')}
</AppButton.GreenButton>
- {showAlert && (
- <RedeemVoucherContainer>
- <RedeemVoucherAlert onClose={onClose} />
- </RedeemVoucherContainer>
- )}
+ <RedeemVoucherContainer>
+ <RedeemVoucherAlert show={showAlert} onClose={onClose} />
+ </RedeemVoucherContainer>
</>
);
}
diff --git a/gui/src/renderer/components/SplitTunnelingSettings.tsx b/gui/src/renderer/components/SplitTunnelingSettings.tsx
index 8f173cab2f..ace08cd694 100644
--- a/gui/src/renderer/components/SplitTunnelingSettings.tsx
+++ b/gui/src/renderer/components/SplitTunnelingSettings.tsx
@@ -183,26 +183,25 @@ function LinuxSplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsProps
{messages.pgettext('split-tunneling-view', 'Find another app')}
</StyledBrowseButton>
- {browseError && (
- <ModalAlert
- type={ModalAlertType.warning}
- iconColor={colors.red}
- message={sprintf(
- // TRANSLATORS: Error message showed in a dialog when an application failes to launch.
- messages.pgettext(
- 'split-tunneling-view',
- 'Unable to launch selection. %(detailedErrorMessage)s',
- ),
- { detailedErrorMessage: browseError },
- )}
- buttons={[
- <AppButton.BlueButton key="close" onClick={hideBrowseFailureDialog}>
- {messages.gettext('Close')}
- </AppButton.BlueButton>,
- ]}
- close={hideBrowseFailureDialog}
- />
- )}
+ <ModalAlert
+ isOpen={browseError !== undefined}
+ type={ModalAlertType.warning}
+ iconColor={colors.red}
+ message={sprintf(
+ // TRANSLATORS: Error message showed in a dialog when an application failes to launch.
+ messages.pgettext(
+ 'split-tunneling-view',
+ 'Unable to launch selection. %(detailedErrorMessage)s',
+ ),
+ { detailedErrorMessage: browseError },
+ )}
+ buttons={[
+ <AppButton.BlueButton key="close" onClick={hideBrowseFailureDialog}>
+ {messages.gettext('Close')}
+ </AppButton.BlueButton>,
+ ]}
+ close={hideBrowseFailureDialog}
+ />
</>
);
}
@@ -279,15 +278,14 @@ function LinuxApplicationRow(props: ILinuxApplicationRowProps) {
<StyledCellWarningIcon source="icon-alert" tintColor={warningColor} width={18} />
)}
</StyledCellButton>
- {showWarning && (
- <ModalAlert
- type={ModalAlertType.warning}
- iconColor={warningColor}
- message={warningMessage}
- buttons={warningDialogButtons}
- close={hideWarningDialog}
- />
- )}
+ <ModalAlert
+ isOpen={showWarning}
+ type={ModalAlertType.warning}
+ iconColor={warningColor}
+ message={warningMessage}
+ buttons={warningDialogButtons}
+ close={hideWarningDialog}
+ />
</>
);
}
diff --git a/gui/src/renderer/components/Support.tsx b/gui/src/renderer/components/Support.tsx
index 16070a1531..b3b7a3e6cf 100644
--- a/gui/src/renderer/components/Support.tsx
+++ b/gui/src/renderer/components/Support.tsx
@@ -132,7 +132,7 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
};
public render() {
- const { sendState, showOutdatedVersionWarning } = this.state;
+ const { sendState } = this.state;
const header = (
<SettingsHeader>
<HeaderTitle>{messages.pgettext('support-view', 'Report a problem')}</HeaderTitle>
@@ -168,8 +168,8 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
{content}
</StyledContentContainer>
- {sendState === SendState.confirm && this.renderNoEmailDialog()}
- {showOutdatedVersionWarning && this.renderOutdateVersionWarningDialog()}
+ {this.renderNoEmailDialog()}
+ {this.renderOutdateVersionWarningDialog()}
</StyledContainer>
</Layout>
);
@@ -247,6 +247,7 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
);
return (
<ModalAlert
+ isOpen={this.state.sendState === SendState.confirm}
type={ModalAlertType.warning}
message={message}
buttons={[
@@ -276,6 +277,7 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
);
return (
<ModalAlert
+ isOpen={this.state.showOutdatedVersionWarning}
type={ModalAlertType.warning}
message={message}
buttons={[
@@ -301,7 +303,7 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
<AppButton.RedButton key="proceed" onClick={this.acknowledgeOutdateVersion}>
{messages.pgettext('support-view', 'Continue anyway')}
</AppButton.RedButton>,
- <AppButton.BlueButton key="cancel" onClick={this.props.onClose}>
+ <AppButton.BlueButton key="cancel" onClick={this.outdatedVersionCancel}>
{messages.gettext('Cancel')}
</AppButton.BlueButton>,
]}
@@ -310,6 +312,11 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
);
}
+ private outdatedVersionCancel = () => {
+ this.acknowledgeOutdateVersion();
+ this.props.onClose();
+ };
+
private renderForm() {
return (
<StyledContent>
diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx
index 123f4b3b42..7456db290d 100644
--- a/gui/src/renderer/components/WireguardSettings.tsx
+++ b/gui/src/renderer/components/WireguardSettings.tsx
@@ -256,7 +256,7 @@ export default class WireguardSettings extends React.Component<IProps, IState> {
</NavigationContainer>
</SettingsContainer>
- {this.state.showMultihopConfirmationDialog && this.renderMultihopConfirmation()}
+ {this.renderMultihopConfirmation()}
</Layout>
);
}
@@ -283,6 +283,7 @@ export default class WireguardSettings extends React.Component<IProps, IState> {
private renderMultihopConfirmation = () => {
return (
<ModalAlert
+ isOpen={this.state.showMultihopConfirmationDialog}
type={ModalAlertType.info}
message={
// TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled.