Fix for interactions outside of modals
This commit is contained in:
Родитель
43d533898d
Коммит
ad3f123a28
|
@ -0,0 +1,161 @@
|
|||
// import { ButtonHTMLAttributes, CSSProperties, DetailedReactHTMLElement, ReactNode, RefObject, cloneElement, forwardRef, isValidElement, useRef } from 'react';
|
||||
// import {AriaButtonProps, AriaOverlayProps, Overlay, useModalOverlay} from 'react-aria';
|
||||
// import { OverlayTriggerProps, OverlayTriggerState } from 'react-stately';
|
||||
|
||||
// https://react-spectrum.adobe.com/react-aria/useModalOverlay.html
|
||||
// Adding a dummy modal with useModalOverlay to see if this solves the problem
|
||||
|
||||
// // export const AliasDeletionButton3701 = () => {
|
||||
// // return (
|
||||
// // <ModalTrigger label="Open Dialog">
|
||||
// // {/* {close => */}
|
||||
// // <Dialog title="Enter your name">
|
||||
// // <form style={{display: 'flex', flexDirection: 'column'}}>
|
||||
// // <label htmlFor="first-name">First Name:</label>
|
||||
// // <input id="first-name" />
|
||||
// // <label htmlFor="last-name">Last Name:</label>
|
||||
// // <input id="last-name" />
|
||||
// // {/* <Button
|
||||
// // onPress={close}
|
||||
// // style={{marginTop: 10}}>
|
||||
// // Submit
|
||||
// // </Button> */}
|
||||
// // </form>
|
||||
// // </Dialog>
|
||||
// // {/* } */}
|
||||
// // </ModalTrigger>
|
||||
// // );
|
||||
// // }
|
||||
|
||||
|
||||
// type ModalProps = {
|
||||
// state: OverlayTriggerState;
|
||||
// children: ReactNode;
|
||||
// };
|
||||
|
||||
// const Modal = (props: ModalProps & AriaOverlayProps) => {
|
||||
// const ref = useRef(null);
|
||||
// const { modalProps, underlayProps } = useModalOverlay(props, props.state, ref);
|
||||
|
||||
// return (
|
||||
// <Overlay>
|
||||
// <div
|
||||
// style={{
|
||||
// position: 'fixed',
|
||||
// zIndex: 100,
|
||||
// top: 0,
|
||||
// left: 0,
|
||||
// bottom: 0,
|
||||
// right: 0,
|
||||
// background: 'rgba(0, 0, 0, 0.5)',
|
||||
// display: 'flex',
|
||||
// alignItems: 'center',
|
||||
// justifyContent: 'center'
|
||||
// }}
|
||||
// {...underlayProps}
|
||||
// >
|
||||
// <p>Testing</p>
|
||||
// <div
|
||||
// {...modalProps}
|
||||
// ref={ref}
|
||||
// style={{
|
||||
// background: 'var(--page-background)',
|
||||
// border: '1px solid gray'
|
||||
// }}
|
||||
// >
|
||||
// {props.children}
|
||||
// </div>
|
||||
// </div>
|
||||
// </Overlay>
|
||||
// );
|
||||
// }
|
||||
|
||||
// import {useOverlayTrigger} from 'react-aria';
|
||||
// import {useOverlayTriggerState} from 'react-stately';
|
||||
// import type {AriaButtonOptions, AriaDialogProps} from 'react-aria';
|
||||
// import {useDialog} from 'react-aria';
|
||||
|
||||
// // Reuse the Button from your component library. See below for details.
|
||||
|
||||
// type TriggerProps = {
|
||||
// label: string;
|
||||
// // children: (close: () => void) => React.ReactElement<string | React.JSXElementConstructor<ReactNode>>;
|
||||
// }
|
||||
|
||||
// // modal trigger
|
||||
// export const AliasDeletionButton3701 = (props: TriggerProps & OverlayTriggerProps & AriaOverlayProps) => {
|
||||
// const state = useOverlayTriggerState(props);
|
||||
// const { triggerProps, overlayProps } = useOverlayTrigger(
|
||||
// { type: 'dialog' },
|
||||
// state
|
||||
// );
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// <Button {...triggerProps}>Open Dialog</Button>
|
||||
// {state.isOpen &&
|
||||
// (
|
||||
// <Modal {...props} state={state}>
|
||||
// {/* {close => */}
|
||||
// <Dialog title="Enter your name">
|
||||
// <form style={{display: 'flex', flexDirection: 'column'}}>
|
||||
// <label htmlFor="first-name">First Name:</label>
|
||||
// <input id="first-name" />
|
||||
// <label htmlFor="last-name">Last Name:</label>
|
||||
// <input id="last-name" />
|
||||
// <Button
|
||||
// onPress={state.close}
|
||||
// style={{marginTop: 10}}>
|
||||
// Submit
|
||||
// </Button>
|
||||
// </form>
|
||||
// </Dialog>
|
||||
// {/* } */}
|
||||
// </Modal>
|
||||
// )}
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
// type DialogProps = {
|
||||
// title?: React.ReactNode;
|
||||
// children: React.ReactNode;
|
||||
// }
|
||||
|
||||
// const Dialog = (props: DialogProps & AriaDialogProps) => {
|
||||
// const ref = useRef(null);
|
||||
// const { dialogProps, titleProps } = useDialog(props, ref);
|
||||
|
||||
// return (
|
||||
// <div {...dialogProps} ref={ref} style={{ padding: 30 }}>
|
||||
// {props.title &&
|
||||
// (
|
||||
// <h3 {...titleProps} style={{ marginTop: 0 }}>
|
||||
// {props.title}
|
||||
// </h3>
|
||||
// )}
|
||||
// {props.children}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// import {useButton} from 'react-aria';
|
||||
|
||||
// type Props = {
|
||||
// children: ReactNode;
|
||||
// style: CSSProperties;
|
||||
// };
|
||||
|
||||
// const Button = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement> & AriaButtonOptions<'button'>>(
|
||||
// (props, buttonRef) => {
|
||||
// const ref = buttonRef as RefObject<HTMLButtonElement>;
|
||||
// const { buttonProps } = useButton(props, ref);
|
||||
// return (
|
||||
// <button {...buttonProps} ref={ref} style={props.style}>
|
||||
// {props.children}
|
||||
// </button>
|
||||
// );
|
||||
// }
|
||||
// );
|
||||
// Button.displayName = "Button";
|
|
@ -35,6 +35,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
|
||||
.dialog-wrapper {
|
||||
background: $color-white;
|
||||
|
|
|
@ -7,9 +7,13 @@ import {
|
|||
useOverlay,
|
||||
usePreventScroll,
|
||||
AriaOverlayProps,
|
||||
useModalOverlay,
|
||||
useOverlayTrigger,
|
||||
Overlay,
|
||||
PressEvent,
|
||||
} from "react-aria";
|
||||
import { useOverlayTriggerState } from "react-stately";
|
||||
import { FormEventHandler, ReactElement, ReactNode, useRef } from "react";
|
||||
import { OverlayTriggerState, useOverlayTriggerState } from "react-stately";
|
||||
import { FormEventHandler, ReactElement, ReactNode, SyntheticEvent, useRef } from "react";
|
||||
import styles from "./AliasDeletionButtonPermanent.module.scss";
|
||||
import { Button } from "../../Button";
|
||||
import { AliasData, getFullAddress } from "../../../hooks/api/aliases";
|
||||
|
@ -19,6 +23,7 @@ import { ErrorTriangleIcon } from "../../Icons";
|
|||
export type Props = {
|
||||
alias: AliasData;
|
||||
onDelete: () => void;
|
||||
modalState: OverlayTriggerState;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -35,10 +40,15 @@ export const AliasDeletionButtonPermanent = (props: Props) => {
|
|||
openModalButtonRef,
|
||||
).buttonProps;
|
||||
|
||||
const modalState = useOverlayTriggerState({});
|
||||
// const modalState = useOverlayTriggerState({});
|
||||
const modalState = props.modalState;
|
||||
const { triggerProps, overlayProps } = useOverlayTrigger({
|
||||
type: 'dialog'
|
||||
}, modalState);
|
||||
|
||||
const cancelButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const cancelButton = useButton(
|
||||
{ onPress: () => modalState.close() },
|
||||
{ onPress: () => { modalState.close() } },
|
||||
cancelButtonRef,
|
||||
);
|
||||
|
||||
|
@ -52,11 +62,14 @@ export const AliasDeletionButtonPermanent = (props: Props) => {
|
|||
};
|
||||
|
||||
const dialog = modalState.isOpen ? (
|
||||
<OverlayContainer>
|
||||
// <OverlayContainer>
|
||||
<Overlay>
|
||||
|
||||
<ConfirmationDialog
|
||||
title={l10n.getString("mask-deletion-header")}
|
||||
onClose={() => modalState.close()}
|
||||
isOpen={modalState.isOpen}
|
||||
modalState={modalState}
|
||||
isDismissable={true}
|
||||
>
|
||||
<samp className={styles["alias-to-delete"]}>
|
||||
|
@ -74,6 +87,7 @@ export const AliasDeletionButtonPermanent = (props: Props) => {
|
|||
{...cancelButton.buttonProps}
|
||||
ref={cancelButtonRef}
|
||||
className={styles["cancel-button"]}
|
||||
onMouseDown={(e) => {e.stopPropagation(); console.log('clicked');}}
|
||||
>
|
||||
{l10n.getString("profile-label-cancel")}
|
||||
</button>
|
||||
|
@ -87,13 +101,15 @@ export const AliasDeletionButtonPermanent = (props: Props) => {
|
|||
</div>
|
||||
</form>
|
||||
</ConfirmationDialog>
|
||||
</OverlayContainer>
|
||||
{/* </OverlayContainer> */}
|
||||
</Overlay>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
{...openModalButtonProps}
|
||||
{...triggerProps}
|
||||
className={styles["deletion-button"]}
|
||||
ref={openModalButtonRef}
|
||||
>
|
||||
|
@ -121,15 +137,17 @@ type ConfirmationDialogProps = {
|
|||
title: string | ReactElement;
|
||||
children: ReactNode;
|
||||
isOpen: boolean;
|
||||
modalState: OverlayTriggerState;
|
||||
onClose?: () => void;
|
||||
};
|
||||
const ConfirmationDialog = (
|
||||
props: ConfirmationDialogProps & AriaOverlayProps,
|
||||
) => {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const { overlayProps, underlayProps } = useOverlay(props, wrapperRef);
|
||||
usePreventScroll();
|
||||
const { modalProps } = useModal();
|
||||
// const { overlayProps, underlayProps } = useOverlay(props, wrapperRef);
|
||||
const { modalProps, underlayProps } = useModalOverlay(props, props.modalState, wrapperRef);
|
||||
// usePreventScroll();
|
||||
// const { modalProps } = useModal();
|
||||
const { dialogProps, titleProps } = useDialog({}, wrapperRef);
|
||||
|
||||
return (
|
||||
|
@ -137,7 +155,7 @@ const ConfirmationDialog = (
|
|||
<FocusScope contain restoreFocus autoFocus>
|
||||
<div
|
||||
className={styles["dialog-wrapper"]}
|
||||
{...overlayProps}
|
||||
// {...overlayProps}
|
||||
{...dialogProps}
|
||||
{...modalProps}
|
||||
ref={wrapperRef}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Localized } from "../../Localized";
|
|||
import { VisuallyHidden } from "../../VisuallyHidden";
|
||||
import { MaskCard } from "./MaskCard";
|
||||
import { isFlagActive } from "../../../functions/waffle";
|
||||
import { OverlayTriggerState } from "react-stately";
|
||||
|
||||
export type Props = {
|
||||
aliases: AliasData[];
|
||||
|
@ -31,6 +32,7 @@ export type Props = {
|
|||
onDelete: (alias: AliasData) => void;
|
||||
onboarding?: boolean;
|
||||
children?: React.ReactNode;
|
||||
modalState: OverlayTriggerState;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -162,6 +164,7 @@ export const AliasList = (props: Props) => {
|
|||
runtimeData={props.runtimeData}
|
||||
isOnboarding={onboarding}
|
||||
copyAfterMaskGeneration={generatedAlias?.id === alias.id}
|
||||
modalState={props.modalState}
|
||||
>
|
||||
{props.children}
|
||||
</MaskCard>
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
OverlayTriggerState,
|
||||
RadioGroupProps,
|
||||
RadioGroupState,
|
||||
useRadioGroupState,
|
||||
|
@ -45,6 +46,7 @@ import { AliasDeletionButton } from "./AliasDeletionButton";
|
|||
import { VisuallyHidden } from "./../../VisuallyHidden";
|
||||
import HorizontalArrow from "./../images/free-onboarding-horizontal-arrow.svg";
|
||||
import { AliasDeletionButtonPermanent } from "./AliasDeletionButtonPermanent";
|
||||
// import { AliasDeletionButton3701 } from "./AliasDeletionButton3701";
|
||||
|
||||
export type Props = {
|
||||
mask: AliasData;
|
||||
|
@ -60,6 +62,7 @@ export type Props = {
|
|||
isOnboarding?: boolean;
|
||||
children?: ReactNode;
|
||||
copyAfterMaskGeneration: boolean;
|
||||
modalState: OverlayTriggerState;
|
||||
};
|
||||
|
||||
export const MaskCard = (props: Props) => {
|
||||
|
@ -388,7 +391,9 @@ export const MaskCard = (props: Props) => {
|
|||
props.runtimeData,
|
||||
"custom_domain_management_redesign",
|
||||
) ? (
|
||||
// <AliasDeletionButton3701 isDismissable={true} label="Open Dialog"/>
|
||||
<AliasDeletionButtonPermanent
|
||||
modalState={props.modalState}
|
||||
onDelete={props.onDelete}
|
||||
alias={props.mask}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
@import "../../styles/tokens.scss";
|
||||
@import "~@mozilla-protocol/core/protocol/css/includes/lib";
|
||||
|
||||
.patch3701 {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -32,12 +32,14 @@ import { isPhonesAvailableInCountry } from "../../functions/getPlan";
|
|||
import { useL10n } from "../../hooks/l10n";
|
||||
import { HolidayPromoBanner } from "./topmessage/HolidayPromoBanner";
|
||||
import { isFlagActive } from "../../functions/waffle";
|
||||
import { OverlayTriggerState } from "react-stately";
|
||||
|
||||
export type Props = {
|
||||
children: ReactNode;
|
||||
// Plain page used for pages without the typical header bag, e.g. tracker report page
|
||||
theme?: "free" | "premium" | "plain";
|
||||
runtimeData?: RuntimeData;
|
||||
modalState?: OverlayTriggerState;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -130,10 +132,26 @@ export const Layout = (props: Props) => {
|
|||
</div>
|
||||
);
|
||||
|
||||
const [pointerEventsNone, setPointerEventsNone] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
let modalState = props.modalState;
|
||||
|
||||
// We want to temporarily disable pointer events as soon as the modal closes.
|
||||
if (!modalState?.isOpen) {
|
||||
const id = setTimeout(() => {
|
||||
setPointerEventsNone(false);
|
||||
}, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
setPointerEventsNone(true)
|
||||
}, [props.modalState?.isOpen, pointerEventsNone])
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageMetadata />
|
||||
<div className={styles.wrapper}>
|
||||
<div className={`${pointerEventsNone && styles.patch3701} ${styles.wrapper}`}>
|
||||
{apiMockWarning}
|
||||
<TopMessage
|
||||
profile={profiles.data?.[0]}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
ReactNode,
|
||||
RefObject,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
FocusScope,
|
||||
|
@ -18,7 +19,7 @@ import {
|
|||
useTooltipTrigger,
|
||||
} from "react-aria";
|
||||
import { event as gaEvent } from "react-ga";
|
||||
import { useMenuTriggerState, useTooltipTriggerState } from "react-stately";
|
||||
import { useMenuTriggerState, useOverlayTriggerState, useTooltipTriggerState } from "react-stately";
|
||||
import { toast } from "react-toastify";
|
||||
import styles from "./profile.module.scss";
|
||||
import UpsellBannerUs from "./images/upsell-banner-us.svg";
|
||||
|
@ -81,6 +82,7 @@ const Profile: NextPage = () => {
|
|||
clearCookie("profile-location-hash");
|
||||
}
|
||||
usePurchaseTracker(profileData.data?.[0]);
|
||||
const modalState = useOverlayTriggerState({});
|
||||
|
||||
if (!userData.isValidating && userData.error) {
|
||||
if (document.location.hash) {
|
||||
|
@ -564,7 +566,7 @@ const Profile: NextPage = () => {
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
<Layout runtimeData={runtimeData.data}>
|
||||
<Layout modalState={modalState} runtimeData={runtimeData.data}>
|
||||
{/* If free user has reached their free mask limit and
|
||||
premium is available in their country, show upsell banner */}
|
||||
{freeMaskLimitReached &&
|
||||
|
@ -590,6 +592,7 @@ const Profile: NextPage = () => {
|
|||
profile={profile}
|
||||
user={user}
|
||||
runtimeData={runtimeData.data}
|
||||
modalState={modalState}
|
||||
/>
|
||||
<p className={styles["size-information"]}>
|
||||
{l10n.getString("profile-supports-email-forwarding", {
|
||||
|
|
Загрузка…
Ссылка в новой задаче