2020-05-27 02:53:31 +02:00
|
|
|
import React, { useRef, createContext, RefObject, useState, useContext } from 'react';
|
|
|
|
import { Cross, AngleLeft } from 'shared/icons';
|
2020-04-10 04:40:22 +02:00
|
|
|
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
2020-05-27 02:53:31 +02:00
|
|
|
import { createPortal } from 'react-dom';
|
|
|
|
import produce from 'immer';
|
|
|
|
import {
|
|
|
|
Container,
|
|
|
|
ContainerDiamond,
|
|
|
|
Header,
|
|
|
|
HeaderTitle,
|
|
|
|
Content,
|
|
|
|
CloseButton,
|
|
|
|
PreviousButton,
|
|
|
|
Wrapper,
|
|
|
|
} from './Styles';
|
|
|
|
|
|
|
|
type PopupContextState = {
|
2020-06-01 04:20:03 +02:00
|
|
|
show: (target: RefObject<HTMLElement>, content: JSX.Element, width?: string | number) => void;
|
2020-05-27 02:53:31 +02:00
|
|
|
setTab: (newTab: number) => void;
|
|
|
|
getCurrentTab: () => number;
|
2020-05-28 03:12:50 +02:00
|
|
|
hide: () => void;
|
2020-05-27 02:53:31 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
type PopupProps = {
|
|
|
|
title: string | null;
|
2020-06-01 04:20:03 +02:00
|
|
|
onClose?: () => void;
|
2020-05-27 02:53:31 +02:00
|
|
|
tab: number;
|
|
|
|
};
|
|
|
|
|
|
|
|
type PopupContainerProps = {
|
|
|
|
top: number;
|
|
|
|
left: number;
|
|
|
|
invert: boolean;
|
|
|
|
onClose: () => void;
|
2020-06-01 04:20:03 +02:00
|
|
|
width?: string | number;
|
2020-05-27 02:53:31 +02:00
|
|
|
};
|
|
|
|
|
2020-06-01 04:20:03 +02:00
|
|
|
const PopupContainer: React.FC<PopupContainerProps> = ({ width, top, left, onClose, children, invert }) => {
|
2020-05-27 02:53:31 +02:00
|
|
|
const $containerRef = useRef();
|
|
|
|
useOnOutsideClick($containerRef, true, onClose, null);
|
|
|
|
return (
|
2020-06-01 04:20:03 +02:00
|
|
|
<Container width={width ?? 316} left={left} top={top} ref={$containerRef} invert={invert}>
|
2020-05-27 02:53:31 +02:00
|
|
|
{children}
|
|
|
|
</Container>
|
|
|
|
);
|
|
|
|
};
|
2020-06-01 04:20:03 +02:00
|
|
|
|
|
|
|
PopupContainer.defaultProps = {
|
|
|
|
width: 316,
|
|
|
|
};
|
|
|
|
|
2020-05-27 02:53:31 +02:00
|
|
|
const PopupContext = createContext<PopupContextState>({
|
|
|
|
show: () => {},
|
|
|
|
setTab: () => {},
|
|
|
|
getCurrentTab: () => 0,
|
2020-05-28 03:12:50 +02:00
|
|
|
hide: () => {},
|
2020-05-27 02:53:31 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
export const usePopup = () => {
|
|
|
|
const ctx = useContext<PopupContextState>(PopupContext);
|
2020-05-28 03:12:50 +02:00
|
|
|
return { showPopup: ctx.show, setTab: ctx.setTab, getCurrentTab: ctx.getCurrentTab, hidePopup: ctx.hide };
|
2020-05-27 02:53:31 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
type PopupState = {
|
|
|
|
isOpen: boolean;
|
|
|
|
left: number;
|
|
|
|
top: number;
|
|
|
|
invert: boolean;
|
|
|
|
currentTab: number;
|
|
|
|
previousTab: number;
|
|
|
|
content: JSX.Element | null;
|
2020-06-01 04:20:03 +02:00
|
|
|
width?: string | number;
|
2020-05-27 02:53:31 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
const { Provider, Consumer } = PopupContext;
|
|
|
|
|
|
|
|
const canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
|
|
|
|
|
|
|
|
const defaultState = {
|
|
|
|
isOpen: false,
|
|
|
|
left: 0,
|
|
|
|
top: 0,
|
|
|
|
invert: false,
|
|
|
|
currentTab: 0,
|
|
|
|
previousTab: 0,
|
|
|
|
content: null,
|
|
|
|
};
|
|
|
|
|
|
|
|
export const PopupProvider: React.FC = ({ children }) => {
|
|
|
|
const [currentState, setState] = useState<PopupState>(defaultState);
|
2020-06-01 04:20:03 +02:00
|
|
|
const show = (target: RefObject<HTMLElement>, content: JSX.Element, width?: number | string) => {
|
2020-05-27 02:53:31 +02:00
|
|
|
if (target && target.current) {
|
|
|
|
const bounds = target.current.getBoundingClientRect();
|
|
|
|
if (bounds.left + 304 + 30 > window.innerWidth) {
|
|
|
|
setState({
|
|
|
|
isOpen: true,
|
|
|
|
left: bounds.left + bounds.width,
|
|
|
|
top: bounds.top + bounds.height,
|
|
|
|
invert: true,
|
|
|
|
currentTab: 0,
|
|
|
|
previousTab: 0,
|
|
|
|
content,
|
2020-06-01 04:20:03 +02:00
|
|
|
width: width ?? 316,
|
2020-05-27 02:53:31 +02:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
setState({
|
|
|
|
isOpen: true,
|
|
|
|
left: bounds.left,
|
|
|
|
top: bounds.top + bounds.height,
|
|
|
|
invert: false,
|
|
|
|
currentTab: 0,
|
|
|
|
previousTab: 0,
|
|
|
|
content,
|
2020-06-01 04:20:03 +02:00
|
|
|
width: width ?? 316,
|
2020-05-27 02:53:31 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-05-28 03:12:50 +02:00
|
|
|
const hide = () => {
|
|
|
|
setState({
|
|
|
|
isOpen: false,
|
|
|
|
left: 0,
|
|
|
|
top: 0,
|
|
|
|
invert: true,
|
|
|
|
currentTab: 0,
|
|
|
|
previousTab: 0,
|
|
|
|
content: null,
|
|
|
|
});
|
|
|
|
};
|
2020-05-27 02:53:31 +02:00
|
|
|
const portalTarget = canUseDOM ? document.body : null; // appease flow
|
|
|
|
|
|
|
|
const setTab = (newTab: number) => {
|
|
|
|
setState((prevState: PopupState) => {
|
|
|
|
return {
|
|
|
|
...prevState,
|
|
|
|
previousTab: currentState.currentTab,
|
|
|
|
currentTab: newTab,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const getCurrentTab = () => {
|
|
|
|
return currentState.currentTab;
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2020-05-28 03:12:50 +02:00
|
|
|
<Provider value={{ hide, show, setTab, getCurrentTab }}>
|
2020-05-27 02:53:31 +02:00
|
|
|
{portalTarget &&
|
|
|
|
currentState.isOpen &&
|
|
|
|
createPortal(
|
|
|
|
<PopupContainer
|
|
|
|
invert={currentState.invert}
|
|
|
|
top={currentState.top}
|
|
|
|
left={currentState.left}
|
|
|
|
onClose={() => setState(defaultState)}
|
2020-06-01 04:20:03 +02:00
|
|
|
width={currentState.width ?? 316}
|
2020-05-27 02:53:31 +02:00
|
|
|
>
|
|
|
|
{currentState.content}
|
|
|
|
<ContainerDiamond invert={currentState.invert} />
|
|
|
|
</PopupContainer>,
|
|
|
|
portalTarget,
|
|
|
|
)}
|
|
|
|
{children}
|
|
|
|
</Provider>
|
|
|
|
);
|
|
|
|
};
|
2020-04-10 04:40:22 +02:00
|
|
|
|
|
|
|
type Props = {
|
2020-05-27 02:53:31 +02:00
|
|
|
title: string | null;
|
2020-04-10 04:40:22 +02:00
|
|
|
top: number;
|
|
|
|
left: number;
|
|
|
|
onClose: () => void;
|
2020-05-27 02:53:31 +02:00
|
|
|
onPrevious?: () => void | null;
|
|
|
|
noHeader?: boolean | null;
|
2020-06-01 04:20:03 +02:00
|
|
|
width?: string | number;
|
2020-04-10 04:40:22 +02:00
|
|
|
};
|
|
|
|
|
2020-06-01 04:20:03 +02:00
|
|
|
const PopupMenu: React.FC<Props> = ({ width, title, top, left, onClose, noHeader, children, onPrevious }) => {
|
2020-04-10 04:40:22 +02:00
|
|
|
const $containerRef = useRef();
|
|
|
|
useOnOutsideClick($containerRef, true, onClose, null);
|
|
|
|
|
|
|
|
return (
|
2020-06-01 04:20:03 +02:00
|
|
|
<Container width={width ?? 316} invert={false} left={left} top={top} ref={$containerRef}>
|
2020-05-27 02:53:31 +02:00
|
|
|
<Wrapper>
|
|
|
|
{onPrevious && (
|
|
|
|
<PreviousButton onClick={onPrevious}>
|
|
|
|
<AngleLeft color="#c2c6dc" />
|
|
|
|
</PreviousButton>
|
|
|
|
)}
|
|
|
|
{noHeader ? (
|
|
|
|
<CloseButton onClick={() => onClose()}>
|
|
|
|
<Cross color="#c2c6dc" />
|
|
|
|
</CloseButton>
|
|
|
|
) : (
|
|
|
|
<Header>
|
|
|
|
<HeaderTitle>{title}</HeaderTitle>
|
|
|
|
<CloseButton onClick={() => onClose()}>
|
|
|
|
<Cross color="#c2c6dc" />
|
|
|
|
</CloseButton>
|
|
|
|
</Header>
|
|
|
|
)}
|
|
|
|
<Content>{children}</Content>
|
|
|
|
</Wrapper>
|
|
|
|
</Container>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const Popup: React.FC<PopupProps> = ({ title, onClose, tab, children }) => {
|
|
|
|
const { getCurrentTab, setTab } = usePopup();
|
|
|
|
if (getCurrentTab() !== tab) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Wrapper>
|
|
|
|
{tab > 0 && (
|
|
|
|
<PreviousButton
|
|
|
|
onClick={() => {
|
|
|
|
setTab(0);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<AngleLeft color="#c2c6dc" />
|
|
|
|
</PreviousButton>
|
|
|
|
)}
|
|
|
|
{title && (
|
|
|
|
<Header>
|
|
|
|
<HeaderTitle>{title}</HeaderTitle>
|
|
|
|
</Header>
|
|
|
|
)}
|
2020-06-01 04:20:03 +02:00
|
|
|
{onClose && (
|
|
|
|
<CloseButton onClick={() => onClose()}>
|
|
|
|
<Cross color="#c2c6dc" />
|
|
|
|
</CloseButton>
|
|
|
|
)}
|
2020-05-27 02:53:31 +02:00
|
|
|
<Content>{children}</Content>
|
|
|
|
</Wrapper>
|
|
|
|
</>
|
2020-04-10 04:40:22 +02:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default PopupMenu;
|