Shared:Components:ModalDialog: fix errors

This commit is contained in:
Timofey Boyko 2023-12-13 18:44:59 +03:00
parent d4225849c1
commit 53460eb49c
7 changed files with 224 additions and 182 deletions

View File

@ -32,7 +32,7 @@ const Template = ({ ...args }: ModalDialogProps) => {
<>
<Button
label="Show"
primary={true}
primary
size={ButtonSize.medium}
onClick={openModal}
/>
@ -53,7 +53,7 @@ const Template = ({ ...args }: ModalDialogProps) => {
<Button
key="SendBtn"
label="Send"
primary={true}
primary
scale
size={ButtonSize.normal}
onClick={() => {
@ -88,6 +88,6 @@ export const Default: Story = {
tablet: ModalDialogType.aside,
mobile: ModalDialogType.aside,
},
children: <></>,
children: <>123</>,
},
};

View File

@ -65,14 +65,20 @@ const Content = styled.div.attrs((props: { modalSwipeOffset?: number }) => ({
visible?: boolean;
}>`
height: auto;
max-height: ${(props) =>
props.autoMaxHeight ? "auto" : props.isLarge ? "400px" : "280px"};
width: ${(props) =>
props.autoMaxWidth ? "auto" : props.isLarge ? "520px" : "400px"};
max-height: ${props.autoMaxHeight
? "auto"
: props.isLarge
? "400px"
: "280px"};
width: ${props.autoMaxWidth
? "auto"
: props.isLarge
? "520px"
: "400px"};
border-radius: 6px;
@media ${mobile} {
transform: translateY(${(props) => (props.visible ? "0" : "100%")});
transform: translateY(${props.visible ? "0" : "100%"});
transition: transform 0.3s ease-in-out;
position: absolute;
bottom: 0;
@ -89,30 +95,25 @@ const Content = styled.div.attrs((props: { modalSwipeOffset?: number }) => ({
top: 0;
bottom: 0;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css<{ visible?: boolean }>`
left: 0;
transform: translateX(
${(props) => (props.visible ? "0" : "-100%")}
);
`
: css<{ visible?: boolean }>`
right: 0;
transform: translateX(
${(props) => (props.visible ? "0" : "100%")}
);
`}
${props.theme.interfaceDirection === "rtl"
? css<{ visible?: boolean }>`
left: 0;
transform: translateX(${props.visible ? "0" : "-100%"});
`
: css<{ visible?: boolean }>`
right: 0;
transform: translateX(${props.visible ? "0" : "100%"});
`}
transition: transform 0.3s ease-in-out;
@media ${mobile} {
transform: translateY(${(props) => (props.visible ? "0" : "100%")});
transform: translateY(${props.visible ? "0" : "100%"});
height: calc(100% - 64px);
width: 100%;
left: 0;
top: ${(props) => (props.embedded ? "0" : "auto")};
top: ${props.embedded ? "0" : "auto"};
right: 0;
top: auto;
bottom: 0;
@ -164,10 +165,9 @@ const StyledBody = styled(Box)<{
${(props) =>
props.isScrollLocked &&
css`
${(props) =>
props.theme.interfaceDirection === "rtl"
? `margin-left: 0 !important;`
: `margin-right: 0 !important;`}
${props.theme.interfaceDirection === "rtl"
? `margin-left: 0 !important;`
: `margin-right: 0 !important;`}
overflow: hidden !important;
`}

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState, useCallback } from "react";
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useState } from "react";
import { isSafari, isTablet } from "react-device-detect";
import throttle from "lodash/throttle";
@ -54,17 +55,18 @@ const ModalDialog = ({
embedded,
withForm,
}: ModalDialogProps) => {
const onCloseEvent = () => {
const onCloseEvent = React.useCallback(() => {
if (embedded) return;
if (isCloseable) onClose?.();
};
}, [embedded, isCloseable, onClose]);
const [currentDisplayType, setCurrentDisplayType] = useState(
getCurrentDisplayType(displayType, displayTypeDetailed),
);
const [modalSwipeOffset, setModalSwipeOffset] = useState(0);
const returnWindowPositionAfterKeyboard = () => {
isSafari && isTablet && window.scrollY !== 0 && window.scrollTo(0, 0);
if (isSafari && isTablet && window.scrollY !== 0) window.scrollTo(0, 0);
};
useEffect(() => {
@ -97,7 +99,7 @@ const ModalDialog = ({
window.removeEventListener("touchmove", onSwipe);
window.addEventListener("touchend", onSwipeEnd);
};
}, []);
}, [displayType, displayTypeDetailed, onClose, onCloseEvent, visible]);
const [header, body, footer, container] = parseChildren(
children,
@ -119,11 +121,11 @@ const ModalDialog = ({
currentDisplayType={currentDisplayType}
withBodyScroll={withBodyScroll}
isScrollLocked={isScrollLocked}
isLarge={isLarge}
isLarge={isLarge || false}
zIndex={zIndex}
autoMaxHeight={autoMaxHeight}
autoMaxWidth={autoMaxWidth}
withFooterBorder={withFooterBorder}
withFooterBorder={withFooterBorder || false}
onClose={onCloseEvent}
isLoading={isLoading}
header={header}

View File

@ -31,18 +31,18 @@ export interface ModalDialogProps {
isCloseable?: boolean;
/** **`MODAL-ONLY`**
Sets `width: 520px` and `max-height: 400px`*/
Sets `width: 520px` and `max-height: 400px` */
isLarge?: boolean;
/** **`MODAL-ONLY`**
Sets `max-width: auto`*/
Sets `max-width: auto` */
autoMaxWidth?: boolean;
/** **`MODAL-ONLY`**
Sets `max-height: auto`*/
Sets `max-height: auto` */
autoMaxHeight?: boolean;
/** **`MODAL-ONLY`**
Displays border betweeen body and footer`*/
Displays border betweeen body and footer` */
withFooterBorder?: boolean;
/** **`ASIDE-ONLY`**
@ -106,3 +106,30 @@ export interface ModalDialogBackdropProps {
visible?: boolean;
modalSwipeOffset?: number;
}
export interface ModalSubComponentsProps {
id?: string;
style?: React.CSSProperties;
className?: string;
currentDisplayType: ModalDialogType;
withBodyScroll?: boolean;
isScrollLocked?: boolean;
isLarge: boolean;
zIndex?: number;
autoMaxHeight?: boolean;
autoMaxWidth?: boolean;
onClose: () => void;
isLoading?: boolean;
header?: React.ReactNode;
body?: React.ReactNode;
footer?: React.ReactNode;
container?: React.ReactNode;
visible?: boolean;
withFooterBorder: boolean;
modalSwipeOffset?: number;
containerVisible?: boolean;
isDoubleFooterLine?: boolean;
isCloseable?: boolean;
embedded?: boolean;
withForm?: boolean;
}

View File

@ -1,4 +1,4 @@
import React, { ReactElement } from "react";
import React from "react";
import { isMobile, isTablet, isTouchDevice } from "../../utils";
import { ModalDialogType } from "./ModalDialog.enums";
@ -27,7 +27,7 @@ export const handleTouchStart = (e: TouchEvent) => {
export const handleTouchMove = (e: TouchEvent, onClose?: () => void) => {
if (!y1) return 0;
let y2 = e.touches[0].clientY;
const y2 = e.touches[0].clientY;
if (y2 - y1 > 120) onClose?.();
return y1 - y2;

View File

@ -28,37 +28,33 @@ const StyledCloseButtonWrapper = styled.div<{
? css`
top: 18px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? `left: -30px;`
: `right: -30px;`}
${props.theme.interfaceDirection === "rtl"
? `left: -30px;`
: `right: -30px;`}
@media ${mobile} {
${(props) =>
props.theme.interfaceDirection === "rtl"
? `left: 10px;`
: `right: 10px;`}
${props.theme.interfaceDirection === "rtl"
? `left: 10px;`
: `right: 10px;`}
top: -27px;
}
`
: css`
top: 18px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? `right: -27px;`
: `left: -27px;`}
${props.theme.interfaceDirection === "rtl"
? `right: -27px;`
: `left: -27px;`}
@media ${mobile} {
top: -27px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
right: auto;
left: 10px;
`
: css`
left: auto;
right: 10px;
`}
${props.theme.interfaceDirection === "rtl"
? css`
right: auto;
left: 10px;
`
: css`
left: auto;
right: 10px;
`}
}
`}

View File

@ -18,6 +18,7 @@ import {
import { CloseButton } from "./CloseButton";
import { ModalBackdrop } from "./ModalBackdrop";
import { FormWrapper } from "./FormWrapper";
import { ModalSubComponentsProps } from "../ModalDialog.types";
const Modal = ({
id,
@ -44,13 +45,57 @@ const Modal = ({
isCloseable,
embedded,
withForm,
}: any) => {
const [windowHeight, setWindowHeight] = React.useState(window.innerHeight);
}: ModalSubComponentsProps) => {
const [windowHeight] = React.useState(window.innerHeight);
const visualPageTop = React.useRef(0);
const diffRef = React.useRef(0);
const contentRef = React.useRef<null | HTMLDivElement>(null);
const onResize = React.useCallback(
(e: Event) => {
if (!contentRef.current || !window.visualViewport) return;
const target = e.target as VisualViewport;
if (currentDisplayType === "modal") {
const diff = windowHeight - target.height - target.pageTop;
visualPageTop.current = target.pageTop;
contentRef.current.style.bottom = `${diff}px`;
return;
}
if (e?.type === "resize") {
const diff = windowHeight - target.height - target.pageTop;
visualPageTop.current = target.pageTop;
contentRef.current.style.bottom = `${diff}px`;
contentRef.current.style.height = `${
target.height - 64 + target.pageTop
}px`;
contentRef.current.style.position = "fixed";
diffRef.current = diff;
} else if (e?.type === "scroll") {
const diff = window.visualViewport.pageTop ? 0 : visualPageTop.current;
contentRef.current.style.bottom = `${diffRef.current + diff}px`;
contentRef.current.style.height = `${
window.visualViewport.height - 64 + diff
}px`;
contentRef.current.style.position = "fixed";
}
},
[currentDisplayType, windowHeight],
);
React.useEffect(() => {
if (isMobileOnly && isIOS && window.visualViewport) {
window.visualViewport.addEventListener("resize", onResize);
@ -62,55 +107,33 @@ const Modal = ({
window.visualViewport.removeEventListener("scroll", onResize);
}
};
}, []);
}, [onResize]);
const onResize = (e: any) => {
if (!contentRef.current || !window.visualViewport) return;
const headerComponent = React.isValidElement(header)
? header.props.children
: null;
const bodyComponent = React.isValidElement(body) ? body.props.children : null;
const footerComponent = React.isValidElement(footer)
? footer.props.children
: null;
const containerComponent = React.isValidElement(container)
? container.props.children
: null;
if (currentDisplayType === "modal") {
let diff = windowHeight - e.target.height - e.target.pageTop;
visualPageTop.current = e.target.pageTop;
contentRef.current.style.bottom = `${diff}px`;
return;
}
if (e?.type === "resize") {
let diff = windowHeight - e.target.height - e.target.pageTop;
visualPageTop.current = e.target.pageTop;
contentRef.current.style.bottom = `${diff}px`;
contentRef.current.style.height = `${
e.target.height - 64 + e.target.pageTop
}px`;
contentRef.current.style.position = "fixed";
diffRef.current = diff;
} else if (e?.type === "scroll") {
const diff = window.visualViewport.pageTop ? 0 : visualPageTop.current;
contentRef.current.style.bottom = `${diffRef.current + diff}px`;
contentRef.current.style.height = `${
window.visualViewport.height - 64 + diff
}px`;
contentRef.current.style.position = "fixed";
}
const validateOnMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
const target = e.target as HTMLDivElement;
if (target.id === "modal-onMouseDown-close") onClose?.();
};
const headerComponent = header ? header.props.children : null;
const bodyComponent = body ? body.props.children : null;
const footerComponent = footer ? footer.props.children : null;
const containerComponent = container ? container.props.children : null;
const validateOnMouseDown = (e: any) => {
if (e.target.id === "modal-onMouseDown-close") onClose();
};
const headerProps = React.isValidElement(header)
? (header?.props as { className?: string })
: { className: "" };
const bodyProps = React.isValidElement(body)
? (body?.props as { className?: string })
: { className: "" };
const footerProps = React.isValidElement(footer)
? (footer?.props as { className?: string })
: { className: "" };
return (
<StyledModal
@ -120,19 +143,19 @@ const Modal = ({
>
<ModalBackdrop
className={visible ? "modal-backdrop-active backdrop-active" : ""}
visible={true}
visible
zIndex={zIndex}
modalSwipeOffset={modalSwipeOffset}
>
<Dialog
id="modal-onMouseDown-close"
className={
classNames(
classNames([
className,
"modalOnCloseBacdrop",
"not-selectable",
"dialog",
) || ""
]) || ""
}
style={style}
onMouseDown={validateOnMouseDown}
@ -167,77 +190,71 @@ const Modal = ({
withFooterBorder={withFooterBorder}
/>
)
) : container &&
containerVisible &&
currentDisplayType !== "modal" ? (
{ containerComponent }
) : (
<>
{container &&
containerVisible &&
currentDisplayType !== "modal" ? (
<>{containerComponent}</>
) : (
<FormWrapper withForm={withForm}>
{header && (
<StyledHeader
id="modal-header-swipe"
className={classNames(
"modal-header",
header.props.className,
)}
currentDisplayType={currentDisplayType}
{...header.props}
>
<Heading
level={1}
className={"heading"}
size={HeadingSize.medium}
truncate={true}
>
{headerComponent}
</Heading>
</StyledHeader>
)}
{body && (
<StyledBody
className={classNames(
"modal-body",
body.props.className,
)}
withBodyScroll={withBodyScroll}
isScrollLocked={isScrollLocked}
hasFooter={1 && footer}
currentDisplayType={currentDisplayType}
{...body.props}
embedded={embedded}
>
{currentDisplayType === "aside" && withBodyScroll ? (
<Scrollbar
stype={ScrollbarType.mediumBlack}
id="modal-scroll"
className="modal-scroll"
>
{bodyComponent}
</Scrollbar>
) : (
bodyComponent
)}
</StyledBody>
)}
{footer && (
<StyledFooter
className={classNames(
"modal-footer",
footer.props.className,
)}
withFooterBorder={withFooterBorder}
currentDisplayType={currentDisplayType}
isDoubleFooterLine={isDoubleFooterLine}
{...footer.props}
>
{footerComponent}
</StyledFooter>
)}
</FormWrapper>
<FormWrapper withForm={withForm || false}>
{header && (
<StyledHeader
id="modal-header-swipe"
className={
classNames(["modal-header", headerProps.className]) ||
"modal-header"
}
{...headerProps}
>
<Heading
level={1}
className="heading"
size={HeadingSize.medium}
truncate
>
{headerComponent}
</Heading>
</StyledHeader>
)}
</>
{body && (
<StyledBody
className={
classNames(["modal-body", bodyProps.className]) ||
"modal-body"
}
withBodyScroll={withBodyScroll}
isScrollLocked={isScrollLocked}
hasFooter={!!footer}
currentDisplayType={currentDisplayType}
{...bodyProps}
// embedded={embedded}
>
{currentDisplayType === "aside" && withBodyScroll ? (
<Scrollbar
stype={ScrollbarType.mediumBlack}
id="modal-scroll"
className="modal-scroll"
>
{bodyComponent}
</Scrollbar>
) : (
bodyComponent
)}
</StyledBody>
)}
{footer && (
<StyledFooter
className={
classNames(["modal-footer", footerProps.className]) ||
"modal-footer"
}
withFooterBorder={withFooterBorder}
isDoubleFooterLine={isDoubleFooterLine}
{...footerProps}
>
{footerComponent}
</StyledFooter>
)}
</FormWrapper>
)}
</Content>
</Dialog>