Skip to content

Commit

Permalink
Impl modal component
Browse files Browse the repository at this point in the history
  • Loading branch information
xorkevin committed Aug 24, 2021
1 parent dbee73e commit 6f8e668
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 2 deletions.
8 changes: 6 additions & 2 deletions example/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,22 @@ import {RecoilRoot} from 'recoil';
import {
ComposeMiddleware,
DarkModeMiddleware,
SnackbarMiddleware,
ModalMiddleware,
PopoverMiddleware,
SnackbarMiddleware,
} from '@xorkevin/nuke';

import App from 'app';

const Middleware = ComposeMiddleware(
DarkModeMiddleware(),
SnackbarMiddleware(),
ModalMiddleware({
root: document.getElementById('popover-portal'),
}),
PopoverMiddleware({
root: document.getElementById('popover-portal'),
}),
SnackbarMiddleware(),
);

ReactDOM.render(
Expand Down
1 change: 1 addition & 0 deletions example/src/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const storiesList = [
'image',
'listgroup',
'menu',
'modal',
'navbar',
'paginate',
'popover',
Expand Down
82 changes: 82 additions & 0 deletions example/src/stories/modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {Fragment} from 'react';

import {Story} from 'docs';

import {Modal, useModal} from '@xorkevin/nuke/src/component/modal';
import Button from '@xorkevin/nuke/src/component/button';

export const Plain = () => {
const modal = useModal();
return (
<Fragment>
<Button forwardedRef={modal.anchorRef} onClick={modal.toggle}>
Anchor
</Button>
{modal.show && (
<Modal anchor={modal.anchor} close={modal.close}>
<div style={{padding: '16px'}}>Hello, world!</div>
</Modal>
)}
</Fragment>
);
};

export const TopRight = () => {
const modal = useModal();
return (
<Fragment>
<Button forwardedRef={modal.anchorRef} onClick={modal.toggle}>
Anchor
</Button>
{modal.show && (
<Modal
alignx="end"
aligny="start"
anchor={modal.anchor}
close={modal.close}
>
<div style={{padding: '16px'}}>Hello, world!</div>
</Modal>
)}
</Fragment>
);
};

export const BottomLeft = () => {
const modal = useModal();
return (
<Fragment>
<Button forwardedRef={modal.anchorRef} onClick={modal.toggle}>
Anchor
</Button>
{modal.show && (
<Modal
alignx="start"
aligny="end"
anchor={modal.anchor}
close={modal.close}
>
<div style={{padding: '16px'}}>Hello, world!</div>
</Modal>
)}
</Fragment>
);
};

const Stories = () => (
<Fragment>
<p>
<code>Modal</code> is used to create modals. The <code>alignx</code> and{' '}
<code>aligny</code> props may be used to position it in the{' '}
<code>center</code>, <code>start</code>, or <code>end</code> of the
screen.
</p>
<Story>
<Plain />
<TopRight />
<BottomLeft />
</Story>
</Fragment>
);

export default Stories;
12 changes: 12 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ import {
MenuHeader,
MenuDivider,
} from './src/component/menu';
import {
ModalDefaultOpts,
ModalCtx,
ModalMiddleware,
Modal,
useModal,
} from './src/component/modal';
import {Navbar, NavItem, NavDivider} from './src/component/navbar';
import usePaginate from './src/component/paginate';
import {
Expand Down Expand Up @@ -126,6 +133,11 @@ export {
MenuItem,
MenuHeader,
MenuDivider,
ModalDefaultOpts,
ModalCtx,
ModalMiddleware,
Modal,
useModal,
Navbar,
NavItem,
NavDivider,
Expand Down
1 change: 1 addition & 0 deletions main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
@import './src/component/listgroup/style';
@import './src/component/maincontent/style';
@import './src/component/menu/style';
@import './src/component/modal/style';
@import './src/component/navbar/style';
@import './src/component/popover/style';
@import './src/component/section/style';
Expand Down
120 changes: 120 additions & 0 deletions src/component/modal/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {
createContext,
useState,
useEffect,
useCallback,
useContext,
} from 'react';
import ReactDOM from 'react-dom';

const positionSet = new Set(['start', 'center', 'end']);

const useStateRef = (val = null) => {
const [ref, setRef] = useState(val);
const cbRef = useCallback(
(node) => {
setRef(node);
},
[setRef],
);
return [ref, cbRef];
};

const ModalDefaultOpts = Object.freeze({
root: document.body,
});

const ModalCtx = createContext(ModalDefaultOpts);

const ModalMiddleware = (value) => {
const v = Object.assign({}, ModalDefaultOpts, value);
return {
ctxProvider: ({children}) => (
<ModalCtx.Provider value={v}>{children}</ModalCtx.Provider>
),
};
};

const useModal = () => {
const [anchor, anchorRef] = useStateRef(null);
const [show, setShow] = useState(false);
const close = useCallback(() => {
setShow(false);
}, [setShow]);
const toggle = useCallback(() => {
setShow((v) => !v);
}, [setShow]);
return {
anchor,
anchorRef,
show,
setShow,
close,
toggle,
};
};

const Modal = ({
className,
alignx,
aligny,
close,
anchor,
onClick,
children,
}) => {
const ctx = useContext(ModalCtx);
const [modal, modalRef] = useStateRef(null);

const clickHandler = useCallback(
(e) => {
if (anchor && anchor.contains(e.target)) {
return;
}
if (modal && modal.contains(e.target)) {
return;
}
if (close) {
close();
}
},
[anchor, modal, close],
);

useEffect(() => {
window.addEventListener('click', clickHandler);
return () => {
window.removeEventListener('click', clickHandler);
};
}, [clickHandler]);

const k = ['modal'];
if (positionSet.has(alignx)) {
k.push(`horizontal-${alignx}`);
} else {
k.push('horizontal-center');
}
if (positionSet.has(aligny)) {
k.push(`vertical-${aligny}`);
} else {
k.push('vertical-center');
}
if (className) {
k.push(className);
}
return ReactDOM.createPortal(
<div ref={modalRef} className={k.join(' ')} onClick={onClick}>
{children}
</div>,
ctx.root,
);
};

export {
Modal as default,
ModalDefaultOpts,
ModalCtx,
ModalMiddleware,
Modal,
useModal,
};
15 changes: 15 additions & 0 deletions src/component/modal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
ModalDefaultOpts,
ModalCtx,
ModalMiddleware,
Modal,
useModal,
} from './base';
export {
Modal as default,
ModalDefaultOpts,
ModalCtx,
ModalMiddleware,
Modal,
useModal,
};
32 changes: 32 additions & 0 deletions src/component/modal/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.modal {
position: fixed;
max-width: calc(100vw - 16px);
max-height: calc(100vh - 16px);
z-index: 112;

&.horizontal-center {
left: 50%;
transform: translateX(-50%);
}

&.horizontal-start {
left: 8px;
}

&.horizontal-end {
right: 8px;
}

&.vertical-center {
top: 50%;
transform: translateY(-50%);
}

&.vertical-start {
top: 8px;
}

&.vertical-end {
bottom: 8px;
}
}

0 comments on commit 6f8e668

Please sign in to comment.