Skip to content

Commit

Permalink
fix: only render modal children after calling showModal (#530)
Browse files Browse the repository at this point in the history
This is important when the children measure their size dynamically when
they are mounted. The OCL editor does it.
  • Loading branch information
targos committed Aug 8, 2023
1 parent d40d20a commit 0a4d3c4
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 55 deletions.
3 changes: 1 addition & 2 deletions src/components/hooks/useOnOff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ export function useOnOff(
initialValue = false,
): [isOn: boolean, setOn: () => void, setOff: () => void, toggle: () => void] {
const [isOn, setOnOff] = useState(initialValue);

const setOn = useCallback(() => setOnOff(true), []);
const setOff = useCallback(() => setOnOff(false), []);
const toggle = useCallback(() => setOnOff(!isOn), [isOn]);
const toggle = useCallback(() => setOnOff((isOn) => !isOn), []);
return [isOn, setOn, setOff, toggle];
}
65 changes: 35 additions & 30 deletions src/components/modal/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function ConfirmModal(props: ConfirmModalProps) {
} = props;

const dialogRef = useRef<HTMLDialogElement>(null);
const dialogProps = useDialog({
const { dialogProps, isModalShown } = useDialog({
dialogRef,
isOpen,
requestCloseOnEsc,
Expand All @@ -104,36 +104,41 @@ export function ConfirmModal(props: ConfirmModalProps) {
return (
<Portal>
<ConfirmModalDialog {...dialogProps} ref={dialogRef}>
<RootLayoutProvider innerRef={portalDomNode}>
<ConfirmModalContents headerColor={headerColor} style={{ maxWidth }}>
<ConfirmModalChildrenRoot headerColor={headerColor}>
{children}
</ConfirmModalChildrenRoot>
{isModalShown && (
<RootLayoutProvider innerRef={portalDomNode}>
<ConfirmModalContents
headerColor={headerColor}
style={{ maxWidth }}
>
<ConfirmModalChildrenRoot headerColor={headerColor}>
{children}
</ConfirmModalChildrenRoot>

<ConfirmModalFooter>
<Button
onClick={onConfirm}
backgroundColor={{
basic: 'hsla(243deg, 75%, 58%, 1)',
hover: 'hsla(245deg, 58%, 50%, 1)',
}}
color={{ basic: 'white' }}
>
{saveText}
</Button>
<Button
onClick={onCancel}
backgroundColor={{
basic: 'hsla(0deg, 72%, 50%, 1)',
hover: 'hsla(0deg, 73%, 42%, 1)',
}}
color={{ basic: 'white' }}
>
{cancelText}
</Button>
</ConfirmModalFooter>
</ConfirmModalContents>
</RootLayoutProvider>
<ConfirmModalFooter>
<Button
onClick={onConfirm}
backgroundColor={{
basic: 'hsla(243deg, 75%, 58%, 1)',
hover: 'hsla(245deg, 58%, 50%, 1)',
}}
color={{ basic: 'white' }}
>
{saveText}
</Button>
<Button
onClick={onCancel}
backgroundColor={{
basic: 'hsla(0deg, 72%, 50%, 1)',
hover: 'hsla(0deg, 73%, 42%, 1)',
}}
color={{ basic: 'white' }}
>
{cancelText}
</Button>
</ConfirmModalFooter>
</ConfirmModalContents>
</RootLayoutProvider>
)}
</ConfirmModalDialog>
</Portal>
);
Expand Down
28 changes: 15 additions & 13 deletions src/components/modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function Modal(props: ModalProps) {
} = props;

const dialogRef = useRef<HTMLDialogElement>(null);
const dialogProps = useDialog({
const { dialogProps, isModalShown } = useDialog({
dialogRef,
isOpen,
requestCloseOnEsc,
Expand All @@ -101,18 +101,20 @@ export function Modal(props: ModalProps) {
return (
<Portal>
<DialogRoot {...dialogProps} ref={dialogRef}>
<RootLayoutProvider innerRef={portalDomNode}>
<DialogContents
style={{
maxWidth,
height: height || 'max-content',
width: width || '100%',
}}
>
{children}
{hasCloseButton && <ModalCloseButton onClick={onRequestClose} />}
</DialogContents>
</RootLayoutProvider>
{isModalShown && (
<RootLayoutProvider innerRef={portalDomNode}>
<DialogContents
style={{
maxWidth,
height: height || 'max-content',
width: width || '100%',
}}
>
{children}
{hasCloseButton && <ModalCloseButton onClick={onRequestClose} />}
</DialogContents>
</RootLayoutProvider>
)}
</DialogRoot>
</Portal>
);
Expand Down
46 changes: 36 additions & 10 deletions src/components/modal/useDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,49 @@ import {
RefObject,
useCallback,
useEffect,
useMemo,
} from 'react';
import { useKbsDisableGlobal } from 'react-kbs';

import { useOnOff } from '../index';

interface UseDialogOptions {
dialogRef: RefObject<HTMLDialogElement>;
isOpen: boolean;
requestCloseOnEsc: boolean;
requestCloseOnBackdrop: boolean;
onRequestClose?: () => void;
}

interface UseDialogReturn {
dialogProps: {
onClick: MouseEventHandler<HTMLDialogElement>;
onCancel: ReactEventHandler<HTMLDialogElement>;
};
isModalShown: boolean;
}

export function useDialog({
dialogRef,
isOpen,
requestCloseOnEsc,
requestCloseOnBackdrop,
onRequestClose,
}: {
dialogRef: RefObject<HTMLDialogElement>;
isOpen: boolean;
requestCloseOnEsc: boolean;
requestCloseOnBackdrop: boolean;
onRequestClose?: () => void;
}) {
}: UseDialogOptions): UseDialogReturn {
useKbsDisableGlobal(isOpen);
const [isModalShown, showModal, hideModal] = useOnOff(false);

useEffect(() => {
const dialog = dialogRef.current;
if (dialog && isOpen) {
showModal();
dialog.showModal();
return () => dialog.close();
return () => {
hideModal();
dialog.close();
};
}
}, [dialogRef, isOpen]);
}, [dialogRef, isOpen, showModal, hideModal]);

const onCancel = useCallback<ReactEventHandler<HTMLDialogElement>>(
(event) => {
Expand Down Expand Up @@ -65,5 +83,13 @@ export function useDialog({
[dialogRef, requestCloseOnBackdrop, onRequestClose],
);

return { onClick, onCancel };
const dialogProps = useMemo<UseDialogReturn['dialogProps']>(
() => ({ onClick, onCancel }),
[onClick, onCancel],
);

return {
dialogProps,
isModalShown,
};
}
18 changes: 18 additions & 0 deletions stories/components/modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
FaNpm,
FaNodeJs,
} from 'react-icons/fa';
import { StructureEditor } from 'react-ocl/full';

import {
Accordion,
Expand Down Expand Up @@ -275,6 +276,23 @@ WithComplexContents.args = {

WithComplexContents.argTypes = actions;

export function DynamicallySizedChildren() {
const [isOpen, open, close] = useOnOff(false);
return (
<div style={{ margin: '2em' }}>
<Button onClick={open}>Open editor modal</Button>
<Modal width={700} height={500} isOpen={isOpen} onRequestClose={close}>
<Modal.Header>Test OCL editor in modal</Modal.Header>
<Modal.Body>
<StructureEditor width={600} height={400} svgMenu />
</Modal.Body>
</Modal>
</div>
);
}

DynamicallySizedChildren.storyName = 'With dynamically sized children';

function DemoPage(props: { openModal: () => void }) {
return (
<>
Expand Down

0 comments on commit 0a4d3c4

Please sign in to comment.