Skip to content

Commit

Permalink
Make photo album modal dismissable
Browse files Browse the repository at this point in the history
This required adding a ref to the modal and making the dropdown use this
ref as the container. It previously used document.body as the container,
meaning any clicks in the dropdown were counted as outside the modal and
closed the modal.
  • Loading branch information
eikhr committed Oct 30, 2024
1 parent 1fe85d2 commit eaba04b
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 46 deletions.
8 changes: 6 additions & 2 deletions app/routes/photos/components/GalleryPictureModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { usePreparedEffect } from '@webkom/react-prepare';
import throttle from 'lodash/throttle';
import { Download, Pencil } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Link, useParams, useNavigate, Outlet } from 'react-router-dom';
import { useSwipeable, RIGHT, LEFT } from 'react-swipeable';
import { updateGalleryCover } from 'app/actions/GalleryActions';
Expand Down Expand Up @@ -148,6 +148,9 @@ const GalleryPictureModal = () => {
const gallery = useAppSelector((state) =>
selectGalleryById<DetailedGallery>(state, galleryId),
);

const modalRef = useRef<HTMLElement>(null);

const actionGrant = gallery?.actionGrant || [];

const isFirstImage =
Expand Down Expand Up @@ -281,10 +284,10 @@ const GalleryPictureModal = () => {
return (
<Modal
onOpenChange={(open) => !open && navigate(`/photos/${gallery.id}`)}
isDismissable={false} // Avoid closing the modal when pressing something from the dropdown
isOpen
contentClassName={styles.content}
aria-label={`Bilde ${picture.id} av ${gallery.title}`}
ref={modalRef}
>
<PropertyHelmet
propertyGenerator={propertyGenerator}
Expand Down Expand Up @@ -320,6 +323,7 @@ const GalleryPictureModal = () => {
closeOnContentClick
className={styles.dropdown}
iconName="ellipsis-horizontal"
container={modalRef.current}
>
<Dropdown.List>
<Dropdown.ListItem>
Expand Down
97 changes: 53 additions & 44 deletions packages/lego-bricks/src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cx from 'classnames';
import { X } from 'lucide-react';
import { forwardRef } from 'react';
import {
Dialog,
Heading,
Expand Down Expand Up @@ -50,49 +51,57 @@ type Props = {
* </DialogTrigger>
* ```
*/
const Modal = ({
isOpen,
onOpenChange,
isDismissable = true,
contentClassName,
dialogRole,
title,
children,
}: Props) => {
return (
<ModalOverlay
isOpen={isOpen}
onOpenChange={onOpenChange}
isDismissable={isDismissable}
isKeyboardDismissDisabled={!isDismissable}
className={styles.overlay}
>
<AriaModal className={cx(styles.modal, contentClassName)}>
<Dialog role={dialogRole} data-test-id="Modal__content">
{({ close }) => (
<>
{title && (
<Heading slot="title" className={styles.title}>
{title}
</Heading>
)}
<Icon
iconNode={<X />}
onPress={close}
className={styles.closeButton}
data-test-id="Modal__closeButton"
// Fix to avoid clicking the element behind the modal when using touch devices
ref={(ref) =>
ref?.addEventListener('touchend', (e) => e.preventDefault())
}
/>
{typeof children === 'function' ? children({ close }) : children}
</>
)}
</Dialog>
</AriaModal>
</ModalOverlay>
);
};
const Modal = forwardRef<HTMLElement, Props>(
(
{
isOpen,
onOpenChange,
isDismissable = true,
contentClassName,
dialogRole,
title,
children,
},
ref,
) => {
return (
<ModalOverlay
isOpen={isOpen}
onOpenChange={onOpenChange}
isDismissable={isDismissable}
isKeyboardDismissDisabled={!isDismissable}
className={styles.overlay}
>
<AriaModal className={cx(styles.modal, contentClassName)}>
<Dialog role={dialogRole} data-test-id="Modal__content" ref={ref}>
{({ close }) => (
<>
{title && (
<Heading slot="title" className={styles.title}>
{title}
</Heading>
)}
<Icon
iconNode={<X />}
onPress={close}
className={styles.closeButton}
data-test-id="Modal__closeButton"
// Fix to avoid clicking the element behind the modal when using touch devices
ref={(ref) =>
ref?.addEventListener('touchend', (e) => e.preventDefault())
}
/>
{typeof children === 'function'
? children({ close })
: children}
</>
)}
</Dialog>
</AriaModal>
</ModalOverlay>
);
},
);
Modal.displayName = 'Modal';

export default Modal;

0 comments on commit eaba04b

Please sign in to comment.