67 lines
1.7 KiB
TypeScript
67 lines
1.7 KiB
TypeScript
import {
|
|
createContext,
|
|
useCallback,
|
|
useMemo,
|
|
useState,
|
|
type ComponentType,
|
|
type ReactNode,
|
|
} from "react";
|
|
import { idAssert } from "../../util/ts.js";
|
|
|
|
export interface PopupManagerContextData {
|
|
openPopup(popup: ComponentType<{ id: string; onClose: () => void }>): string;
|
|
openPopup<T>(
|
|
popup: ComponentType<{ id: string; onClose: () => void } & T>,
|
|
props: T
|
|
): string;
|
|
|
|
closePopup(id: string): void;
|
|
}
|
|
|
|
export const PopupManagerContext =
|
|
createContext<PopupManagerContextData | null>(null);
|
|
PopupManagerContext.displayName = "PopupManagerContext";
|
|
|
|
type Popup = {
|
|
id: string;
|
|
Component: ComponentType<{ id: string; onClose: () => void }>;
|
|
props: { id: string; onClose: () => void };
|
|
};
|
|
|
|
export function PopupManager({ children }: { children: ReactNode }) {
|
|
const [popups, setPopups] = useState<Popup[]>([]);
|
|
|
|
const openPopup = useCallback<PopupManagerContextData["openPopup"]>(
|
|
(
|
|
Component: ComponentType<{ id: string; onClose: () => void }>,
|
|
props = {}
|
|
) => {
|
|
const id = crypto.randomUUID();
|
|
Object.assign(props, {
|
|
id,
|
|
onClose: () => setPopups((prev) => prev.filter((x) => x.id !== id)),
|
|
});
|
|
idAssert<{ id: string; onClose: () => void }>(props);
|
|
setPopups((prev) => [...prev, { id, Component, props }]);
|
|
return id;
|
|
},
|
|
[]
|
|
);
|
|
const closePopup = useCallback((id: string) => {
|
|
setPopups((prev) => prev.filter((x) => x.id !== id));
|
|
}, []);
|
|
const ctx = useMemo<PopupManagerContextData>(
|
|
() => ({ openPopup, closePopup }),
|
|
[]
|
|
);
|
|
|
|
return (
|
|
<PopupManagerContext value={ctx}>
|
|
{children}
|
|
{popups.map((popup) => (
|
|
<popup.Component key={popup.id} {...popup.props} />
|
|
))}
|
|
</PopupManagerContext>
|
|
);
|
|
}
|