Skip to content

Commit

Permalink
feat: add OverlaysManager
Browse files Browse the repository at this point in the history
  • Loading branch information
yyyyaaa committed Jul 21, 2024
1 parent 98866d6 commit d06f2e3
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 4 deletions.
2 changes: 2 additions & 0 deletions packages/react/.storybook/WithThemeDecorator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ThemeProvider,
Select,
SelectOption,
OverlaysManager,
useTheme,
} from "../src";
import { DEFAULT_ACCENTS, Accent } from "../src/styles/tokens";
Expand Down Expand Up @@ -109,6 +110,7 @@ const WithThemeDecorator = (props) => {
// }}
>
<ThemeShell {...props} />
<OverlaysManager />
</ThemeProvider>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import {
import Box from "@/ui/box";
import ChainSwapInput from "@/ui/chain-swap-input";
import ChainListItem from "@/ui/chain-list-item";
import { closestBodyElement } from "@/helpers/platform";

import * as styles from "./chain-swap-combobox.css";
import type { ChainListItemProps } from "@/ui/chain-list-item/chain-list-item.types";
import type { Sprinkles } from "@/styles/rainbow-sprinkles.css";
import { overlays } from "@/ui/overlays-manager/overlays";
import useTheme from "@/ui/hooks/use-theme";

interface ItemProps {
Expand Down Expand Up @@ -212,7 +212,7 @@ export default function ChainSwapCombobox(props: ChainSwapComboboxProps) {
return setMountRoot(props.rootNode);
}

setMountRoot(closestBodyElement(containerRef.current));
setMountRoot(overlays.getOrCreateOverlayRoot(containerRef.current));
}, []);

return (
Expand Down
17 changes: 16 additions & 1 deletion packages/react/scaffolds/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import clx from "clsx";
import React, { cloneElement, forwardRef } from "react";
import useTheme from "../hooks/use-theme";
import { overlays } from "@/ui/overlays-manager/overlays";
import * as styles from "./modal.css";

interface DialogOptions {
Expand Down Expand Up @@ -149,6 +150,10 @@ const Modal = forwardRef<HTMLDivElement, ModalProps>((props, forwardedRef) => {
childrenClassName,
} = props;

const [defaultRoot, setDefaultRoot] = React.useState<HTMLElement | null>(
null,
);

const dialog = useDialog({
initialOpen,
open: isOpen,
Expand All @@ -166,6 +171,16 @@ const Modal = forwardRef<HTMLDivElement, ModalProps>((props, forwardedRef) => {

const { styles: transitionStyles } = useTransitionStyles(dialog.context);

React.useEffect(() => {
// User-provided root
if (root) {
return;
}

// Default lib root
setDefaultRoot(overlays.getOrCreateOverlayRoot(window.document));
}, []);

React.useEffect(() => {
dialog.setLabelId(id);
return () => dialog.setLabelId(undefined);
Expand All @@ -186,7 +201,7 @@ const Modal = forwardRef<HTMLDivElement, ModalProps>((props, forwardedRef) => {
{typeof renderTrigger === "function" ? renderTrigger(triggerProps) : null}

{dialog.open && (
<FloatingPortal root={root}>
<FloatingPortal root={root ? root : defaultRoot}>
<FloatingOverlay
className={clx(themeClassName)}
lockScroll={preventScroll}
Expand Down
12 changes: 11 additions & 1 deletion packages/react/scaffolds/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
selectFullWidth,
} from "./select.css";
import { Item, SelectContext, SelectContextValue } from "./select.context";
import { overlays } from "@/ui/overlays-manager/overlays";

const DEFAULT_LIST_WIDTH = "220";

Expand Down Expand Up @@ -142,6 +143,15 @@ export default function Select(props: SelectProps) {
[elementsRef, handleSelect],
);

const [defaultRoot, setDefaultRoot] = React.useState<HTMLElement | null>(
null,
);

React.useEffect(() => {
// Default lib root
setDefaultRoot(overlays.getOrCreateOverlayRoot(window.document));
}, []);

React.useEffect(() => {
if (!!props.defaultSelectedItem) {
handleSelect(props.defaultSelectedItem);
Expand Down Expand Up @@ -235,7 +245,7 @@ export default function Select(props: SelectProps) {
</div>

<SelectContext.Provider value={selectContext}>
<FloatingPortal>
<FloatingPortal root={defaultRoot}>
<div
ref={refs.setFloating}
tabIndex={-1}
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export { default as ClipboardCopyText } from "./ui/clipboard-copy-text";
export type { ClipboardCopyTextProps } from "./ui/clipboard-copy-text/clipboard-copy-text.types";
export { default as ThemeProvider } from "./ui/theme-provider";
export type { ThemeProviderProps } from "./ui/theme-provider/theme-provider.types";
export { default as OverlaysManager } from "./ui/overlays-manager";
export type { OverlaysManagerProps } from "./ui/overlays-manager/overlays-manager.types";
export { default as InterchainUIProvider } from "./ui/interchain-ui-provider";
export { default as FadeIn } from "./ui/fade-in";
export { default as PoolsHeader } from "./ui/pools-header";
Expand Down
1 change: 1 addition & 0 deletions src/ui/overlays-manager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./overlays-manager.lite";
67 changes: 67 additions & 0 deletions src/ui/overlays-manager/overlays-manager.lite.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useRef, onMount, onUnMount, useMetadata } from "@builder.io/mitosis";
import { overlays } from "./overlays";
import { OverlaysManagerProps } from "./overlays-manager.types";

useMetadata({
rsc: {
componentType: "client",
},
});

export default function OverlaysManager(props: OverlaysManagerProps) {
const containerRef = useRef<HTMLDivElement>(null);
let cleanupRef = useRef<(() => void) | null>(null);

onMount(() => {
if (containerRef) {
const ownerDocument = containerRef.ownerDocument;
const overlayRoot = overlays.getOrCreateOverlayRoot(ownerDocument);

// Append children to the overlay root
while (containerRef.firstChild) {
overlayRoot.appendChild(containerRef.firstChild);
}

let zIndexCounter = 1;

// Function to apply styles to direct children
const applyStylesToChildren = () => {
Array.from(overlayRoot.children).forEach((child, index) => {
if (child instanceof HTMLElement) {
child.style.position = "relative";
child.style.zIndex = (index + 1).toString();
}
});
zIndexCounter = overlayRoot.children.length + 1;
};

// Apply initial styles
applyStylesToChildren();

// Set up MutationObserver to watch for changes in children
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "childList") {
applyStylesToChildren();
}
});
});

observer.observe(overlayRoot, { childList: true });

// Cleanup function
cleanupRef = () => {
observer.disconnect();
while (containerRef.firstChild) {
containerRef.removeChild(containerRef.firstChild);
}
};
}
});

onUnMount(() => {
if (typeof cleanupRef === "function") cleanupRef();
});

return <div ref={containerRef} style={{ display: "none" }} />;
}
1 change: 1 addition & 0 deletions src/ui/overlays-manager/overlays-manager.types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export interface OverlaysManagerProps {}
41 changes: 41 additions & 0 deletions src/ui/overlays-manager/overlays.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export const OVERLAYS_MANAGER_ID = "interchain-ui-overlays-manager";

class Overlays {
private static instance: Overlays;
private overlayRoots: Map<Document, HTMLElement> = new Map();

private constructor() {}

public static getInstance(): Overlays {
if (!Overlays.instance) {
Overlays.instance = new Overlays();
}
return Overlays.instance;
}

public getOrCreateOverlayRoot(ownerDocument: Document): HTMLElement {
if (!this.overlayRoots.has(ownerDocument)) {
const root = ownerDocument.createElement("div");
root.id = OVERLAYS_MANAGER_ID;
// root.style.position = "fixed";
// root.style.top = "0";
// root.style.left = "0";
// root.style.width = "100%";
// root.style.height = "100%";
// root.style.pointerEvents = "none";
// root.style.zIndex = "1000";
ownerDocument.body.appendChild(root);
this.overlayRoots.set(ownerDocument, root);
}
return this.overlayRoots.get(ownerDocument)!;
}

public cleanup() {
this.overlayRoots.forEach((root, doc) => {
doc.body.removeChild(root);
});
this.overlayRoots.clear();
}
}

export const overlays = Overlays.getInstance();

0 comments on commit d06f2e3

Please sign in to comment.