Skip to content

Commit

Permalink
feat(@clayui/modal): add useModal hook
Browse files Browse the repository at this point in the history
  • Loading branch information
matuzalemsteles committed Aug 21, 2019
1 parent 03ea1c1 commit 4ff2b3d
Show file tree
Hide file tree
Showing 6 changed files with 403 additions and 283 deletions.
77 changes: 27 additions & 50 deletions packages/clay-modal/src/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,25 @@ import classNames from 'classnames';
import Context, {IContext} from './Context';
import Footer from './Footer';
import Header from './Header';
import React, {FunctionComponent, useEffect, useRef, useState} from 'react';
import React, {FunctionComponent, useEffect, useRef} from 'react';
import {ClayPortal} from '@clayui/shared';
import {Size} from './types';
import {Observer, ObserverType, Size} from './types';
import {useUserInteractions} from './Hook';

interface IProps extends React.HTMLAttributes<HTMLDivElement>, IContext {
children?: (onClose: () => void) => React.ReactNode;

interface IProps
extends React.HTMLAttributes<HTMLDivElement>,
Omit<IContext, 'onClose'> {
/**
* The size of element modal.
*/
size?: Size;
}

const delay = (fn: Function) => {
return setTimeout(() => {
fn();
}, 100);
};

const modalOpenClassName = 'modal-open';
/**
* Observer is Modal's communication system with `useModal`
* hook, adds observer from `useModal` hook here.
*/
observer: Observer;
}

const ClayModal: FunctionComponent<IProps> & {
Body: typeof Body;
Expand All @@ -38,58 +36,32 @@ const ClayModal: FunctionComponent<IProps> & {
} = ({
children,
className,
onClose = () => {},
observer,
size,
spritemap,
status,
...otherProps
}: IProps) => {
const [visibleClassShow, setVisibleClassShow] = useState<boolean>(false);

const modalBodyElementRef = useRef<HTMLDivElement | null>(null);
const modalDialogElementRef = useRef<HTMLDivElement | null>(null);

/**
* Control the close of the modal to create the component's "unmount"
* animation and call the onClose prop with delay.
*/
const handleCloseModal = () => {
document.body.classList.remove(modalOpenClassName);
setVisibleClassShow(false);

delay(onClose);
};

const context = {
onClose: handleCloseModal,
spritemap,
status,
};

useUserInteractions(modalDialogElementRef, handleCloseModal);

useEffect(() => {
document.body.classList.add(modalOpenClassName);
const timer = delay(() => {
setVisibleClassShow(true);
});
useUserInteractions(modalDialogElementRef, () =>
observer.dispatch(ObserverType.Close)
);

return () => {
clearTimeout(timer);
};
}, []);
useEffect(() => observer.dispatch(ObserverType.Open), []);

return (
<ClayPortal subPortalRef={modalBodyElementRef}>
<div
className={classNames('modal-backdrop fade', {
show: visibleClassShow,
show: observer.mutation,
})}
/>
<div
{...otherProps}
className={classNames('fade modal d-block', className, {
show: visibleClassShow,
show: observer.mutation,
})}
ref={modalBodyElementRef}
>
Expand All @@ -102,10 +74,15 @@ const ClayModal: FunctionComponent<IProps> & {
tabIndex={-1}
>
<div className="modal-content">
<Context.Provider value={context}>
{children && typeof children === 'function'
? children(handleCloseModal)
: children}
<Context.Provider
value={{
onClose: () =>
observer.dispatch(ObserverType.Close),
spritemap,
status,
}}
>
{children}
</Context.Provider>
</div>
</div>
Expand Down
33 changes: 20 additions & 13 deletions packages/clay-modal/src/__tests__/IncrementalInteractions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
*/

import Button from '@clayui/button';
import ClayModal from '..';
import React, {useState} from 'react';
import ClayModal, {ClayModalProvider, Context, useModal} from '..';
import React, {useContext, useEffect, useState} from 'react';
import ReactDOM from 'react-dom';
import {cleanup, fireEvent, render} from '@testing-library/react';

Expand All @@ -24,15 +24,12 @@ const ModalWithState: React.FunctionComponent<IProps> = ({
...props
}) => {
const [visible, setVisible] = useState(initialVisible);
const {observer} = useModal({onClose: () => setVisible(false)});

return (
<>
{visible && (
<ClayModal
onClose={() => setVisible(false)}
spritemap={spritemap}
{...props}
>
<ClayModal observer={observer} spritemap={spritemap} {...props}>
{children}
</ClayModal>
)}
Expand Down Expand Up @@ -160,19 +157,29 @@ describe('Modal -> IncrementalInteractions', () => {
});

it('close the modal when click on the button of Footer component', () => {
const {getByLabelText} = render(
<ModalWithState initialVisible>
{(onClose: any) => (
const ModalState = () => {
const [visible, setVisible] = useState(true);
const {observer, onClose} = useModal({
onClose: () => setVisible(false),
});

if (!visible) {
return null;
}

return (
<ClayModal observer={observer} spritemap={spritemap}>
<ClayModal.Footer
last={
<Button aria-label="buttonFooter" onClick={onClose}>
{'Foo'}
</Button>
}
/>
)}
</ModalWithState>
);
</ClayModal>
);
};
const {getByLabelText} = render(<ModalState />);

jest.runAllTimers();

Expand Down
Loading

0 comments on commit 4ff2b3d

Please sign in to comment.