Created
April 27, 2019 15:36
-
-
Save blvdmitry/c87e29a5ab7988efff6fd91015954680 to your computer and use it in GitHub Desktop.
Dialog implementation with hooks
This file contains hidden or 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
import React from 'react'; | |
import { storiesOf } from '@storybook/react'; | |
import Button from 'components/Button'; | |
import Dialog from 'components/Dialog'; | |
import DialogProvider from 'components/Dialog/DialogProvider'; | |
const Story = (props: { position?: 'right' }) => { | |
const modalId = 'testModal'; | |
const { show } = Dialog.use(modalId); | |
return ( | |
<React.Fragment> | |
<Button text="Show modal" onClick={show} /> | |
<Dialog | |
position={props.position} | |
id={modalId} | |
title="Save template" | |
subtitle="Set cadence, add topic and editors for future use" | |
actions={[{ text: 'Create' }]} | |
> | |
<div style={{ height: 400, background: '#f3f3f3' }} /> | |
</Dialog> | |
</React.Fragment> | |
); | |
}; | |
storiesOf('Dialog', module) | |
.add('Default', () => <DialogProvider><Story /></DialogProvider>) | |
.add('Right', () => <DialogProvider><Story position="right" /></DialogProvider>); |
This file contains hidden or 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
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
import classnames from 'classnames'; | |
import useOnClickOutside from 'use-onclickoutside'; | |
import Button from 'components/Button'; | |
import Group from 'components/Group'; | |
import Icon from 'components/Icon'; | |
import context, { useDialog } from './DialogContext'; | |
import * as T from './Dialog.types'; | |
import s from './Dialog.pcss'; | |
const Dialog = (props: T.Props) => { | |
const { title, subtitle, actions, children, id, position } = props; | |
const rootClassNames = classnames(s.root, position && s[`--position-${position}`]); | |
const { setPosition } = React.useContext(context); | |
const rootRef = React.useRef(null); | |
const { hide, ref, active } = Dialog.use(id); | |
React.useEffect(() => { | |
if (active) setPosition(position || null); | |
}, [position, active]); | |
useOnClickOutside(rootRef, hide); | |
if (!active || !ref || !ref.current) return null; | |
const result = ( | |
<div className={rootClassNames} ref={rootRef}> | |
<div className={s.title}>{ title }</div> | |
{ subtitle && <div className={s.subtitle}>{ subtitle }</div> } | |
{ children && <div className={s.content}>{ children }</div> } | |
{ | |
actions && ( | |
<div className={s.actions}> | |
<Group inline> | |
{ actions.map(action => <Button {...action} key={action.text} />) } | |
</Group> | |
</div> | |
) | |
} | |
<button className={s.close} onClick={hide}> | |
<Icon name="close" size="medium" /> | |
</button> | |
</div> | |
); | |
return ReactDOM.createPortal(result, ref.current); | |
}; | |
Dialog.use = useDialog; | |
export default Dialog; |
This file contains hidden or 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
import React from 'react'; | |
import * as T from './Dialog.types'; | |
const error = 'You\'re using the dialog outside of the DialogProvider'; | |
const context = React.createContext<T.ContextData>({ | |
show: () => console.error(error), | |
hide: () => console.error(error), | |
setPosition: () => console.log(error), | |
activeId: null, | |
position: null, | |
ref: null, | |
}); | |
export const useDialog = (id: string) => { | |
const { show, hide, activeId, ref } = React.useContext(context); | |
return { | |
show: () => show(id), | |
hide, | |
ref, | |
active: activeId === id, | |
}; | |
}; | |
export default context; |
This file contains hidden or 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
import React from 'react'; | |
import classnames from 'classnames'; | |
import CssTransition from 'react-transition-group/CSSTransition'; | |
import Overlay from 'components/Overlay'; | |
import DialogContext from './DialogContext'; | |
import * as T from './Dialog.types'; | |
import s from './DialogProvider.pcss'; | |
const DialogProvider = (props: T.ProviderProps) => { | |
const ref = React.useRef(null); | |
const [activeId, setActiveId] = React.useState<string | null>(null); | |
const [visible, setVisible] = React.useState(false); | |
const [position, setPosition] = React.useState<T.Position | null>(null); | |
const timeout = 300; | |
const show: T.Show = async (id) => { | |
await setActiveId(id); | |
// Visible is triggered after id is set and Dialog defines its position | |
setVisible(true); | |
}; | |
const hide = () => { | |
setVisible(false); | |
setTimeout(() => setActiveId(null), timeout); | |
}; | |
const positionClassName = position ? s[`--animator-position-${position}`] : s['--animator-position-center']; | |
const activeClassName = s['--animator-active']; | |
const animatedClassName = s['--animator-animated']; | |
return ( | |
<DialogContext.Provider value={{ hide, show, setPosition, activeId, ref, position }}> | |
{ props.children } | |
<Overlay onClose={hide} active={visible}> | |
<div className={s.root}> | |
<CssTransition | |
timeout={timeout} | |
in={visible} | |
classNames={{ | |
enter: positionClassName, | |
enterActive: classnames(positionClassName, activeClassName, animatedClassName), | |
enterDone: classnames(positionClassName, activeClassName, animatedClassName), | |
exit: classnames(positionClassName, animatedClassName), | |
exitActive: classnames(positionClassName, animatedClassName), | |
exitDone: classnames(positionClassName), | |
}} | |
> | |
<div ref={ref} className={s.animator} /> | |
</CssTransition> | |
</div> | |
</Overlay> | |
</DialogContext.Provider> | |
); | |
}; | |
export default DialogProvider; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment