Skip to content

Instantly share code, notes, and snippets.

@zenVentzi
Created March 18, 2019 15:03
Show Gist options
  • Save zenVentzi/571f707f4ab7d6d4bbbdd4a11e90fa7a to your computer and use it in GitHub Desktop.
Save zenVentzi/571f707f4ab7d6d4bbbdd4a11e90fa7a to your computer and use it in GitHub Desktop.
import styled from "styled-components";
import React, { useRef, useLayoutEffect, useState } from "react";
import ReactDOM from "react-dom";
import shortid from "shortid";
// https://stackoverflow.com/a/42337722/4132182
function getNodeIndex(element: Element) {
if (!element.parentNode) {
throw Error("parent node not found");
}
return Array.from(element.parentNode.childNodes).indexOf(element);
}
type Portal = {
children: React.ReactElement;
};
enum Progress {
measure_dom,
portal,
done
}
const Portal = (props: Portal) => {
const spanRef = useRef<HTMLSpanElement>(null);
const parentRef = useRef<HTMLElement>();
const childRef = useRef<HTMLElement>();
const childPosition = useRef<ClientRect | DOMRect>();
const modalRoot = useRef<HTMLDivElement>();
const [progress, setProgress] = useState<Progress>(Progress.measure_dom);
useLayoutEffect(() => {
console.log(`outer, ${progress}`);
if (progress !== Progress.done && modalRoot.current) {
console.log(`inner`);
modalRoot.current.appendChild(childRef.current!);
childRef.current!.style.position = "absolute";
childRef.current!.style.top = `${childPosition.current!.top +
window.scrollY}px`;
childRef.current!.style.left = `${childPosition.current!.left}px`;
childRef.current!.style.width = `${childPosition.current!.width}px`;
childRef.current!.style.height = `${childPosition.current!.height}px`;
setProgress(Progress.done);
}
if (progress !== Progress.measure_dom) return;
parentRef.current = spanRef.current!.parentElement!;
childRef.current = spanRef.current!.firstElementChild! as HTMLElement;
parentRef.current.insertBefore(childRef.current, spanRef.current);
childPosition.current = childRef.current.getBoundingClientRect();
setProgress(Progress.portal);
modalRoot.current = document.createElement("div");
modalRoot.current.id = `modal_${shortid()}`;
document.body.appendChild(modalRoot.current);
return () => {
if (progress === Progress.done) {
document.body.removeChild(modalRoot.current!);
}
};
}, [progress]);
if (progress === Progress.measure_dom) {
return <span ref={spanRef}>{props.children}</span>;
}
return ReactDOM.createPortal(null, modalRoot.current!);
};
export default Portal;
/*
1) render with span
2) spanChild
3) spanParent
4) change firstChild parent
5) remove span
6) firstChildPosition
7) portal firstChild
8) set firstChild position when in portal
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment