Optima: portals

This commit is contained in:
Enrico Ros
2024-07-29 22:45:52 -07:00
parent 7a5a24f210
commit 8d6540289d
3 changed files with 120 additions and 0 deletions
@@ -0,0 +1,18 @@
import * as React from 'react';
import { createPortal } from 'react-dom';
import { OptimaPortalId, usePortalElement } from './store-optima-portals';
export function OptimaPortalIn(props: {
targetPortalId: OptimaPortalId;
children: React.ReactNode;
}) {
// react to the portal being made available
const portalElement = usePortalElement(props.targetPortalId);
if (!portalElement)
return null;
return createPortal(props.children, portalElement);
}
@@ -0,0 +1,57 @@
import { create } from 'zustand';
// module configuration
const DEBUG_OPTIMA_PORTALS = false;
export type OptimaPortalId =
| 'optima-portal-drawer'
| 'optima-portal-properties'
| 'optima-portal-toolbar'
;
interface PortalState {
portalElements: Map<OptimaPortalId, HTMLElement>;
}
interface PortalActions {
addPortal: (id: OptimaPortalId, element: HTMLElement) => void;
removePortal: (id: OptimaPortalId) => void;
}
type PortalStore = PortalState & PortalActions;
const useOptimaPortalsStore = create<PortalStore>((set, get) => ({
// init state
portalElements: new Map(),
// actions
addPortal: (id, element) => set((state) => {
const newPortals = new Map(state.portalElements);
newPortals.set(id, element);
if (DEBUG_OPTIMA_PORTALS)
console.log(' > store.addPortal', id, !!element);
return { portalElements: newPortals };
}),
removePortal: (id) => set((state) => {
const newPortals = new Map(state.portalElements);
newPortals.delete(id);
if (DEBUG_OPTIMA_PORTALS)
console.log(' < store.removePortal', id);
return { portalElements: newPortals };
}),
}));
export function optimaPortalsActions(): PortalActions {
return useOptimaPortalsStore.getState();
}
export function usePortalElement(id: OptimaPortalId) {
return useOptimaPortalsStore((state) => state.portalElements.get(id) || null);
}
@@ -0,0 +1,45 @@
import * as React from 'react';
import { OptimaPortalId, optimaPortalsActions } from './store-optima-portals';
/**
* Note: this hook assumes that the ref is created by when useLayoutEffect is called,
* and will warn otherwise.
*/
export function useOptimaPortalOut(portalTargetId: OptimaPortalId, debugCallerName: string) {
// state
const ref = React.useRef<HTMLElement>(null);
React.useLayoutEffect(() => {
const { addPortal, removePortal } = optimaPortalsActions();
if (!ref.current) {
console.warn(`useOptimaPortalOut: ref.current is null for type ${portalTargetId} (called by ${debugCallerName})`);
} else {
addPortal(portalTargetId, ref.current);
}
return () => removePortal(portalTargetId);
}, [debugCallerName, portalTargetId]);
return ref;
}
/*
// This version will add the portal only when really getting the ref
export function useOptimaPortalOut(portalTargetId: OptimaPortalId, debugCallerName: string) {
const setRef = React.useCallback((node: HTMLElement | null) => {
const { addPortal, removePortal } = optimaPortalsActions();
console.log('useOptimaPortalOut.setRef', portalTargetId, node);
if (node) {
console.log(' - useOptimaPortalOut call AddPortal', portalTargetId);
addPortal(portalTargetId, node);
} else {
removePortal(portalTargetId);
}
}, [portalTargetId]);
return setRef;
}
*/