import "./drawer.scss"; import React from "react"; import { createPortal } from "react-dom"; import { cssNames, noop } from "../../utils"; import { Icon } from "../icon"; import { Animate, AnimateName } from "../animate"; import { browserHistory } from "../../navigation"; import { themeStore } from "../../theme.store"; export interface DrawerProps { open: boolean; title: React.ReactNode; size?: string; // e.g. 50%, 500px, etc. usePortal?: boolean; className?: string | object; contentClass?: string | object; position?: "top" | "left" | "right" | "bottom"; animation?: AnimateName; onClose?: () => void; toolbar?: React.ReactNode; } const defaultProps: Partial = { position: "right", animation: "slide-right", usePortal: false, onClose: noop, }; export class Drawer extends React.Component { static defaultProps = defaultProps as object; private mouseDownTarget: HTMLElement private contentElem: HTMLElement private scrollElem: HTMLElement private scrollPos = new Map(); private stopListenLocation = browserHistory.listen(() => { this.restoreScrollPos(); }); componentDidMount(): void { window.addEventListener("mousedown", this.onMouseDown); window.addEventListener("click", this.onClickOutside); window.addEventListener("keydown", this.onEscapeKey); } componentWillUnmount(): void { this.stopListenLocation(); window.removeEventListener("mousedown", this.onMouseDown); window.removeEventListener("click", this.onClickOutside); window.removeEventListener("keydown", this.onEscapeKey); } saveScrollPos = (): void => { if (!this.scrollElem) { return; } const key = browserHistory.location.key; this.scrollPos.set(key, this.scrollElem.scrollTop); } restoreScrollPos = (): void => { if (!this.scrollElem) { return; } const key = browserHistory.location.key; this.scrollElem.scrollTop = this.scrollPos.get(key) || 0; } onEscapeKey = (evt: KeyboardEvent): void=> { if (!this.props.open) { return; } if (evt.code === "Escape") { this.close(); } } onClickOutside = (evt: MouseEvent): void => { const { contentElem, mouseDownTarget, close, props: { open } } = this; if (!open || evt.defaultPrevented || contentElem.contains(mouseDownTarget)) { return; } const clickedElem = evt.target as HTMLElement; const isOutsideAnyDrawer = !clickedElem.closest('.Drawer'); if (isOutsideAnyDrawer) { close(); } this.mouseDownTarget = null; } onMouseDown = (evt: MouseEvent): void => { if (this.props.open) { this.mouseDownTarget = evt.target as HTMLElement; } } close = (): void => { const { open, onClose } = this.props; if (open) { onClose(); } } render(): JSX.Element { const { open, position, title, animation, children, toolbar, size, usePortal } = this.props; let { className, contentClass } = this.props; className = cssNames("Drawer", className, position, themeStore.activeTheme.type); contentClass = cssNames("drawer-content flex column box grow", contentClass); const style: React.CSSProperties = size ? { ["--size" as string]: size } : undefined; const drawer = (
{ this.contentElem = e; }} >
{title}
{toolbar}
{ this.scrollElem = e; }} > {children}
); return usePortal ? createPortal(drawer, document.body) : drawer; } }