94 lines
2.3 KiB
TypeScript
94 lines
2.3 KiB
TypeScript
import React, { createContext, FC, ReactNode, useEffect, useMemo, useState } from 'react';
|
|
import { omit } from 'ramda';
|
|
|
|
export type Modal<P> = {
|
|
Component?: FC<P>;
|
|
id: string;
|
|
props?: Partial<P>;
|
|
style?: {
|
|
overrideMaxWidth?: boolean;
|
|
width?: number | string;
|
|
};
|
|
title?: string | ReactNode;
|
|
};
|
|
|
|
export type ModalId = Modal<any>['id'];
|
|
|
|
export type ModalContextType = {
|
|
close: (modalId: ModalId) => void;
|
|
open: <P>(modalId: ModalId, modal?: Partial<Modal<P>>) => void;
|
|
opened: ModalId[];
|
|
register: <P>(modal: Modal<P>) => void;
|
|
registered: Record<ModalId, Modal<any>>;
|
|
unregister: (modalId: ModalId) => void;
|
|
};
|
|
|
|
export const ModalContext = createContext<ModalContextType>({
|
|
registered: {},
|
|
opened: [],
|
|
open: () => {},
|
|
close: () => {},
|
|
register: () => {},
|
|
unregister: () => {},
|
|
});
|
|
|
|
export const ModalWrapper: FC<{ children: ReactNode }> = ({ children }) => {
|
|
const [registered, setRegistered] = useState<Record<ModalId, Modal<any>>>({});
|
|
const [opened, setOpened] = useState<ModalId[]>([]);
|
|
|
|
const register = function <P>(modal: Modal<P>) {
|
|
setRegistered((modals) => ({
|
|
...modals,
|
|
[modal.id]: {
|
|
...modals[modal.id],
|
|
...modal,
|
|
},
|
|
}));
|
|
};
|
|
|
|
const unregister = (modalId: ModalId) => {
|
|
setRegistered((modals) => omit([modalId], modals));
|
|
};
|
|
|
|
const open = function <P>(modalId: ModalId, modal: Modal<P> | undefined) {
|
|
if (!registered[modalId]) {
|
|
register({ ...modal, id: modalId });
|
|
}
|
|
setOpened((opened) => [...opened, modalId]);
|
|
};
|
|
|
|
const close = (modalId: ModalId) => {
|
|
setOpened((opened) => opened.filter((id) => id !== modalId));
|
|
};
|
|
|
|
useEffect(() => {
|
|
window.addEventListener('openModal', (e: CustomEventInit<Modal<any>>) => {
|
|
if (e.detail) open(e.detail.id, e.detail);
|
|
});
|
|
window.addEventListener('closeModal', (e: CustomEventInit<ModalId>) => {
|
|
if (e.detail) close(e.detail);
|
|
});
|
|
}, []);
|
|
|
|
const value = useMemo(
|
|
() => ({
|
|
registered: registered,
|
|
opened,
|
|
unregister,
|
|
register,
|
|
open,
|
|
close,
|
|
}),
|
|
[registered, opened],
|
|
);
|
|
|
|
return <ModalContext.Provider value={value}>{children}</ModalContext.Provider>;
|
|
};
|
|
|
|
export const openModal = (modal: Modal<any>) => {
|
|
window.dispatchEvent(new CustomEvent<Modal<any>>('openModal', { detail: modal }));
|
|
};
|
|
|
|
export const closeModal = (modalId: ModalId) => {
|
|
window.dispatchEvent(new CustomEvent<ModalId>('closeModal', { detail: modalId }));
|
|
};
|