Skip to content

Commit

Permalink
feat(Modal): new Modal components building blocks (#2432)
Browse files Browse the repository at this point in the history
  • Loading branch information
YossiSaadi authored Dec 2, 2024
1 parent 8c7458e commit 43b6b42
Show file tree
Hide file tree
Showing 26 changed files with 1,173 additions and 5 deletions.
2 changes: 2 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@
"monday-ui-style": "0.21.0",
"prop-types": "^15.8.1",
"react-dates": "21.8.0",
"react-focus-lock": "^2.13.2",
"react-inlinesvg": "^4.1.3",
"react-is": "^16.9.0",
"react-popper": "^2.3.0",
"react-remove-scroll": "^2.6.0",
"react-select": "npm:react-select-module@^3.2.5",
"react-transition-group": "^4.4.5",
"react-virtualized-auto-sizer": "^1.0.7",
Expand Down
77 changes: 77 additions & 0 deletions packages/core/src/components/ModalNew/Modal/Modal.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
.overlay {
position: fixed;
inset: 0;
background-color: rgba(41, 47, 76, 0.7);
}

.modal {
position: fixed;
top: 50%;
left: 50%;

display: flex;
flex-direction: column;
width: var(--modal-width, 50%);
max-height: var(--modal-max-height, 80%);
background-color: var(--primary-background-color);
overflow: hidden;
border-radius: var(--border-radius-big);
box-shadow: var(--box-shadow-large);

&.sizeSmall {
--modal-max-height: 50%;
--modal-width: 45%;
}

&.sizeMedium {
--modal-max-height: 80%;
--modal-width: 50%;
}

&.sizeLarge {
--modal-max-height: 80%;
--modal-width: 70%;
}

@media (min-width: 1280px) {
&.sizeSmall {
--modal-width: 40%;
}

&.sizeMedium {
--modal-width: 44%;
}

&.sizeLarge {
--modal-width: 66%;
}
}

@media (min-width: 1440px) {
&.sizeSmall {
--modal-width: 35%;
}

&.sizeMedium {
--modal-width: 38%;
}

&.sizeLarge {
--modal-width: 60%;
}
}

@media (min-width: 1720px) {
&.sizeSmall {
--modal-width: 34%;
}

&.sizeMedium {
--modal-width: 36%;
}

&.sizeLarge {
--modal-width: 58%;
}
}
}
129 changes: 129 additions & 0 deletions packages/core/src/components/ModalNew/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { forwardRef, useCallback, useMemo, useState } from "react";
import cx from "classnames";
import { RemoveScroll } from "react-remove-scroll";
import FocusLock from "react-focus-lock";
import { motion, AnimatePresence } from "framer-motion";
import { getTestId } from "../../../tests/test-ids-utils";
import { ComponentDefaultTestId } from "../../../tests/constants";
import styles from "./Modal.module.scss";
import { ModalProps } from "./Modal.types";
import ModalTopActions from "../ModalTopActions/ModalTopActions";
import { getStyle } from "../../../helpers/typesciptCssModulesHelper";
import { camelCase } from "lodash-es";
import { ModalProvider } from "../context/ModalContext";
import { ModalContextProps } from "../context/ModalContext.types";
import useKeyEvent from "../../../hooks/useKeyEvent";
import { keyCodes } from "../../../constants";
import {
modalAnimationAnchorPopVariants,
modalAnimationCenterPopVariants,
modalAnimationOverlayVariants
} from "../utils/animationVariants";

const Modal = forwardRef(
(
{
id,
show,
size = "medium",
renderHeaderAction,
closeButtonTheme,
closeButtonAriaLabel,
onClose = () => {},
anchorElementRef,
children,
className,
"data-testid": dataTestId
}: ModalProps,
ref: React.ForwardedRef<HTMLDivElement>
) => {
const [titleId, setTitleId] = useState<string>();
const [descriptionId, setDescriptionId] = useState<string>();

const setTitleIdCallback = useCallback((id: string) => setTitleId(id), []);
const setDescriptionIdCallback = useCallback((id: string) => setDescriptionId(id), []);

const contextValue = useMemo<ModalContextProps>(
() => ({
modalId: id,
setTitleId: setTitleIdCallback,
setDescriptionId: setDescriptionIdCallback
}),
[id, setTitleIdCallback, setDescriptionIdCallback]
);

const onBackdropClick = useCallback<React.MouseEventHandler<HTMLDivElement>>(
e => {
if (!show) return;
onClose(e);
},
[onClose, show]
);

const onEscClick = useCallback<React.KeyboardEventHandler<HTMLBodyElement>>(
e => {
if (!show) return;
onClose(e);
},
[onClose, show]
);

useKeyEvent({
callback: onEscClick,
capture: true,
keys: [keyCodes.ESCAPE]
});

const modalAnimationVariants = anchorElementRef?.current
? modalAnimationAnchorPopVariants
: modalAnimationCenterPopVariants;

return (
<AnimatePresence>
{show && (
<ModalProvider value={contextValue}>
<RemoveScroll>
<motion.div
variants={modalAnimationOverlayVariants}
initial="exit"
animate="enter"
exit="exit"
data-testid={getTestId(ComponentDefaultTestId.MODAL_NEXT_OVERLAY, id)}
className={styles.overlay}
onClick={onBackdropClick}
aria-hidden
/>
<FocusLock returnFocus>
<motion.div
variants={modalAnimationVariants}
initial="exit"
animate="enter"
exit="exit"
custom={anchorElementRef}
ref={ref}
className={cx(styles.modal, getStyle(styles, camelCase("size-" + size)), className)}
id={id}
data-testid={dataTestId || getTestId(ComponentDefaultTestId.MODAL_NEXT, id)}
role="dialog"
aria-modal
aria-labelledby={titleId}
aria-describedby={descriptionId}
>
<ModalTopActions
renderAction={renderHeaderAction}
color={closeButtonTheme}
closeButtonAriaLabel={closeButtonAriaLabel}
onClose={onClose}
/>
{children}
</motion.div>
</FocusLock>
</RemoveScroll>
</ModalProvider>
)}
</AnimatePresence>
);
}
);

export default Modal;
21 changes: 21 additions & 0 deletions packages/core/src/components/ModalNew/Modal/Modal.types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { VibeComponentProps } from "../../../types";
import React from "react";
import { ModalTopActionsProps } from "../ModalTopActions/ModalTopActions.types";

export type ModalSize = "small" | "medium" | "large";

export type ModalCloseEvent =
| React.MouseEvent<HTMLDivElement | HTMLButtonElement>
| React.KeyboardEvent<HTMLBodyElement>;

export interface ModalProps extends VibeComponentProps {
id: string;
show: boolean;
size?: ModalSize;
closeButtonTheme?: ModalTopActionsProps["color"];
closeButtonAriaLabel?: ModalTopActionsProps["closeButtonAriaLabel"];
onClose?: (event: ModalCloseEvent) => void;
renderHeaderAction?: ModalTopActionsProps["renderAction"];
anchorElementRef?: React.RefObject<HTMLElement>;
children: React.ReactNode;
}
Loading

0 comments on commit 43b6b42

Please sign in to comment.