-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
269 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ const storiesList = [ | |
'image', | ||
'listgroup', | ||
'menu', | ||
'modal', | ||
'navbar', | ||
'paginate', | ||
'popover', | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |