Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Modal): new Modal components building blocks #2432

Merged
merged 17 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c675806
feat(ModalHeader): header component to be used in modal (#2413)
YossiSaadi Sep 11, 2024
66d900c
feat(ModalContent): content component to be used in modal by consumer…
YossiSaadi Sep 11, 2024
3e6faca
feat(ModalTopActions): top actions component for internal use (#2415)
YossiSaadi Sep 24, 2024
7c8207a
feat(Modal): modal component with basic functionality (#2417)
YossiSaadi Sep 24, 2024
8d71703
feat(Modal): implement size prop and responsiveness (#2419)
YossiSaadi Sep 24, 2024
c0c09c8
feat(Modal): use titleId and descriptionId for appropriate arias, all…
YossiSaadi Sep 24, 2024
fd52e71
feat(Modal): handle onClose and show logic of component (#2429)
YossiSaadi Sep 25, 2024
d26c72d
feat(Modal): disable scroll outside of modal when it is open (#2433)
YossiSaadi Sep 25, 2024
02da7c3
feat(Modal): keep focus inside opened modal, return focus to last ele…
YossiSaadi Sep 26, 2024
298a4d4
feat(ModalTopActions): use CloseMedium instead of CloseSmall icon (#2…
YossiSaadi Sep 26, 2024
fe98f40
feat(ModalHeader): set titleId and descriptionId for Modal (#2446)
YossiSaadi Oct 9, 2024
23b868f
feat(ModalFooterBase): base footer component to be used by different …
YossiSaadi Oct 9, 2024
993e9f4
feat(Modal): control autofocus and keyboard navigation focus (#2556)
YossiSaadi Oct 28, 2024
9b53547
feat(Modal): add pop from center/anchor animations (#2489)
YossiSaadi Oct 30, 2024
ad1b51f
Merge branch 'refs/heads/master' into feat/yossi/new-modal-building-b…
YossiSaadi Nov 18, 2024
e71ab86
Merge branch 'refs/heads/master' into feat/yossi/new-modal-building-b…
YossiSaadi Dec 2, 2024
5382219
fix(Modal): use @vibe/icons
YossiSaadi Dec 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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