Files
abode/src/react/contexts/PopupManager.tsx
T
2026-06-29 23:07:14 +00:00

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>
);
}