smart modal positioning
This commit is contained in:
parent
a11030814c
commit
b76e1df819
|
@ -51,6 +51,7 @@ export interface SquiggleChartProps {
|
||||||
maxX?: number;
|
maxX?: number;
|
||||||
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
||||||
distributionChartActions?: boolean;
|
distributionChartActions?: boolean;
|
||||||
|
enableLocalSettings?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOnChange = () => {};
|
const defaultOnChange = () => {};
|
||||||
|
@ -76,6 +77,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
color,
|
color,
|
||||||
title,
|
title,
|
||||||
distributionChartActions,
|
distributionChartActions,
|
||||||
|
enableLocalSettings = false,
|
||||||
}) => {
|
}) => {
|
||||||
const result = useSquiggle({
|
const result = useSquiggle({
|
||||||
code,
|
code,
|
||||||
|
@ -111,6 +113,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
chartSettings={chartSettings}
|
chartSettings={chartSettings}
|
||||||
environment={environment ?? defaultEnvironment}
|
environment={environment ?? defaultEnvironment}
|
||||||
|
enableLocalSettings={enableLocalSettings}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ const SquiggleContext = React.createContext<SquiggleContextShape>({
|
||||||
|
|
||||||
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
||||||
const context = useContext(SquiggleContext);
|
const context = useContext(SquiggleContext);
|
||||||
|
|
||||||
if (context.containerized) {
|
if (context.containerized) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
import React, { FC, useState, useEffect, useMemo } from "react";
|
import React, {
|
||||||
|
FC,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
} from "react";
|
||||||
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
|
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { useMaybeControlledValue } from "../lib/hooks";
|
import { useMaybeControlledValue } from "../lib/hooks";
|
||||||
|
@ -229,6 +236,13 @@ const useRunnerState = (code: string) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PlaygroundContextShape = {
|
||||||
|
getLeftPanelElement: () => HTMLDivElement | undefined;
|
||||||
|
};
|
||||||
|
export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
|
||||||
|
getLeftPanelElement: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
defaultCode = "",
|
defaultCode = "",
|
||||||
height = 500,
|
height = 500,
|
||||||
|
@ -301,6 +315,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
{...vars}
|
{...vars}
|
||||||
bindings={defaultBindings}
|
bindings={defaultBindings}
|
||||||
jsImports={imports}
|
jsImports={imports}
|
||||||
|
enableLocalSettings={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -345,40 +360,54 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
</StyledTab.Panels>
|
</StyledTab.Panels>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const leftPanelRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const withEditor = (
|
const withEditor = (
|
||||||
<div className="flex mt-2">
|
<div className="flex mt-2">
|
||||||
<div className="w-1/2">{tabs}</div>
|
<div
|
||||||
|
className="w-1/2 relative"
|
||||||
|
style={{ minHeight: height }}
|
||||||
|
ref={leftPanelRef}
|
||||||
|
>
|
||||||
|
{tabs}
|
||||||
|
</div>
|
||||||
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
|
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const withoutEditor = <div className="mt-3">{tabs}</div>;
|
const withoutEditor = <div className="mt-3">{tabs}</div>;
|
||||||
|
|
||||||
|
const getLeftPanelElement = useCallback(() => {
|
||||||
|
return leftPanelRef.current ?? undefined;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SquiggleContainer>
|
<SquiggleContainer>
|
||||||
<StyledTab.Group>
|
<PlaygroundContext.Provider value={{ getLeftPanelElement }}>
|
||||||
<div className="pb-4">
|
<StyledTab.Group>
|
||||||
<div className="flex justify-between items-center">
|
<div className="pb-4">
|
||||||
<StyledTab.List>
|
<div className="flex justify-between items-center">
|
||||||
<StyledTab
|
<StyledTab.List>
|
||||||
name={vars.showEditor ? "Code" : "Display"}
|
<StyledTab
|
||||||
icon={vars.showEditor ? CodeIcon : EyeIcon}
|
name={vars.showEditor ? "Code" : "Display"}
|
||||||
|
icon={vars.showEditor ? CodeIcon : EyeIcon}
|
||||||
|
/>
|
||||||
|
<StyledTab name="Sampling Settings" icon={CogIcon} />
|
||||||
|
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
||||||
|
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
||||||
|
</StyledTab.List>
|
||||||
|
<RunControls
|
||||||
|
autorunMode={autorunMode}
|
||||||
|
isStale={renderedCode !== code}
|
||||||
|
run={run}
|
||||||
|
isRunning={isRunning}
|
||||||
|
onAutorunModeChange={setAutorunMode}
|
||||||
/>
|
/>
|
||||||
<StyledTab name="Sampling Settings" icon={CogIcon} />
|
</div>
|
||||||
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
{vars.showEditor ? withEditor : withoutEditor}
|
||||||
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
|
||||||
</StyledTab.List>
|
|
||||||
<RunControls
|
|
||||||
autorunMode={autorunMode}
|
|
||||||
isStale={renderedCode !== code}
|
|
||||||
run={run}
|
|
||||||
isRunning={isRunning}
|
|
||||||
onAutorunModeChange={setAutorunMode}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{vars.showEditor ? withEditor : withoutEditor}
|
</StyledTab.Group>
|
||||||
</div>
|
</PlaygroundContext.Provider>
|
||||||
</StyledTab.Group>
|
|
||||||
</SquiggleContainer>
|
</SquiggleContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { CogIcon } from "@heroicons/react/solid";
|
import { CogIcon } from "@heroicons/react/solid";
|
||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useRef, useState, useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { Modal } from "../ui/Modal";
|
import { Modal } from "../ui/Modal";
|
||||||
|
@ -10,6 +10,7 @@ import {
|
||||||
defaultColor,
|
defaultColor,
|
||||||
defaultTickFormat,
|
defaultTickFormat,
|
||||||
} from "../../lib/distributionSpecBuilder";
|
} from "../../lib/distributionSpecBuilder";
|
||||||
|
import { PlaygroundContext } from "../SquigglePlayground";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
path: Path;
|
path: Path;
|
||||||
|
@ -18,12 +19,15 @@ type Props = {
|
||||||
withFunctionSettings: boolean;
|
withFunctionSettings: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ItemSettingsModal: React.FC<Props & { close: () => void }> = ({
|
const ItemSettingsModal: React.FC<
|
||||||
|
Props & { close: () => void; resetScroll: () => void }
|
||||||
|
> = ({
|
||||||
path,
|
path,
|
||||||
onChange,
|
onChange,
|
||||||
disableLogX,
|
disableLogX,
|
||||||
withFunctionSettings,
|
withFunctionSettings,
|
||||||
close,
|
close,
|
||||||
|
resetScroll,
|
||||||
}) => {
|
}) => {
|
||||||
const { setSettings, getSettings, getMergedSettings } =
|
const { setSettings, getSettings, getMergedSettings } =
|
||||||
useContext(ViewerContext);
|
useContext(ViewerContext);
|
||||||
|
@ -51,7 +55,7 @@ const ItemSettingsModal: React.FC<Props & { close: () => void }> = ({
|
||||||
diagramCount: mergedSettings.chartSettings.count,
|
diagramCount: mergedSettings.chartSettings.count,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = watch((vars) => {
|
const subscription = watch((vars) => {
|
||||||
const settings = getSettings(path); // get the latest version
|
const settings = getSettings(path); // get the latest version
|
||||||
setSettings(path, {
|
setSettings(path, {
|
||||||
|
@ -78,10 +82,26 @@ const ItemSettingsModal: React.FC<Props & { close: () => void }> = ({
|
||||||
return () => subscription.unsubscribe();
|
return () => subscription.unsubscribe();
|
||||||
}, [getSettings, setSettings, onChange, path, watch]);
|
}, [getSettings, setSettings, onChange, path, watch]);
|
||||||
|
|
||||||
|
const { getLeftPanelElement } = useContext(PlaygroundContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal>
|
<Modal container={getLeftPanelElement()} close={close}>
|
||||||
<Modal.Header close={close}>
|
<Modal.Header>
|
||||||
Chart settings{path.length ? " for " + pathAsString(path) : ""}
|
Chart settings
|
||||||
|
{path.length ? (
|
||||||
|
<>
|
||||||
|
{" for "}
|
||||||
|
<span
|
||||||
|
title="Scroll to item"
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={resetScroll}
|
||||||
|
>
|
||||||
|
{pathAsString(path)}
|
||||||
|
</span>{" "}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<ViewSettings
|
<ViewSettings
|
||||||
|
@ -97,11 +117,25 @@ const ItemSettingsModal: React.FC<Props & { close: () => void }> = ({
|
||||||
|
|
||||||
export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const { setSettings, getSettings } = useContext(ViewerContext);
|
const { enableLocalSettings, setSettings, getSettings } =
|
||||||
|
useContext(ViewerContext);
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
if (!enableLocalSettings) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const settings = getSettings(props.path);
|
const settings = getSettings(props.path);
|
||||||
|
|
||||||
|
const resetScroll = () => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
window.scroll({
|
||||||
|
top: ref.current.getBoundingClientRect().y + window.scrollY,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2" ref={ref}>
|
||||||
<CogIcon
|
<CogIcon
|
||||||
className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500"
|
className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
@ -122,7 +156,11 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<ItemSettingsModal {...props} close={() => setIsOpen(false)} />
|
<ItemSettingsModal
|
||||||
|
{...props}
|
||||||
|
close={() => setIsOpen(false)}
|
||||||
|
resetScroll={resetScroll}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,6 +9,7 @@ type ViewerContextShape = {
|
||||||
getSettings(path: Path): LocalItemSettings;
|
getSettings(path: Path): LocalItemSettings;
|
||||||
getMergedSettings(path: Path): MergedItemSettings;
|
getMergedSettings(path: Path): MergedItemSettings;
|
||||||
setSettings(path: Path, value: LocalItemSettings): void;
|
setSettings(path: Path, value: LocalItemSettings): void;
|
||||||
|
enableLocalSettings: boolean; // show local settings icon in the UI
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewerContext = React.createContext<ViewerContextShape>({
|
export const ViewerContext = React.createContext<ViewerContextShape>({
|
||||||
|
@ -30,4 +31,5 @@ export const ViewerContext = React.createContext<ViewerContextShape>({
|
||||||
height: 150,
|
height: 150,
|
||||||
}),
|
}),
|
||||||
setSettings() {},
|
setSettings() {},
|
||||||
|
enableLocalSettings: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,6 +23,7 @@ type Props = {
|
||||||
chartSettings: FunctionChartSettings;
|
chartSettings: FunctionChartSettings;
|
||||||
/** Environment for further function executions */
|
/** Environment for further function executions */
|
||||||
environment: environment;
|
environment: environment;
|
||||||
|
enableLocalSettings?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Settings = {
|
type Settings = {
|
||||||
|
@ -38,6 +39,7 @@ export const SquiggleViewer: React.FC<Props> = ({
|
||||||
distributionPlotSettings,
|
distributionPlotSettings,
|
||||||
chartSettings,
|
chartSettings,
|
||||||
environment,
|
environment,
|
||||||
|
enableLocalSettings = false,
|
||||||
}) => {
|
}) => {
|
||||||
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
||||||
const settingsRef = useRef<Settings>({});
|
const settingsRef = useRef<Settings>({});
|
||||||
|
@ -85,6 +87,7 @@ export const SquiggleViewer: React.FC<Props> = ({
|
||||||
getSettings,
|
getSettings,
|
||||||
setSettings,
|
setSettings,
|
||||||
getMergedSettings,
|
getMergedSettings,
|
||||||
|
enableLocalSettings,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{result.tag === "Ok" ? (
|
{result.tag === "Ok" ? (
|
||||||
|
|
|
@ -1,20 +1,35 @@
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import * as React from "react";
|
import React, { useContext } from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import { XIcon } from "@heroicons/react/solid";
|
import { XIcon } from "@heroicons/react/solid";
|
||||||
|
import { SquiggleContainer } from "../SquiggleContainer";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useWindowScroll, useWindowSize } from "react-use";
|
||||||
|
import { rectToClientRect } from "@floating-ui/core";
|
||||||
|
|
||||||
const Overlay: React.FC = () => (
|
type ModalContextShape = {
|
||||||
<motion.div
|
close: () => void;
|
||||||
className="absolute inset-0 -z-10 bg-black"
|
};
|
||||||
initial={{ opacity: 0 }}
|
const ModalContext = React.createContext<ModalContextShape>({
|
||||||
animate={{ opacity: 0.3 }}
|
close: () => undefined,
|
||||||
/>
|
});
|
||||||
);
|
|
||||||
|
const Overlay: React.FC = () => {
|
||||||
|
const { close } = useContext(ModalContext);
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 -z-10 bg-black"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.1 }}
|
||||||
|
onClick={close}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ModalHeader: React.FC<{
|
const ModalHeader: React.FC<{
|
||||||
close: () => void;
|
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}> = ({ children, close }) => {
|
}> = ({ children }) => {
|
||||||
|
const { close } = useContext(ModalContext);
|
||||||
return (
|
return (
|
||||||
<header className="px-5 py-3 border-b border-gray-200 font-bold flex items-center justify-between">
|
<header className="px-5 py-3 border-b border-gray-200 font-bold flex items-center justify-between">
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
|
@ -41,22 +56,92 @@ const ModalFooter: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
<div className="px-5 py-3 border-t border-gray-200">{children}</div>
|
<div className="px-5 py-3 border-t border-gray-200">{children}</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ModalWindow: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
const ModalWindow: React.FC<{
|
||||||
<div
|
children: React.ReactNode;
|
||||||
className="bg-white rounded shadow-toast overflow-auto flex flex-col mx-2 w-96"
|
container?: HTMLElement;
|
||||||
style={{ maxHeight: "calc(100% - 20px)", maxWidth: "calc(100% - 20px)" }}
|
}> = ({ children, container }) => {
|
||||||
>
|
// This component works in two possible modes:
|
||||||
{children}
|
// 1. container mode - the modal is rendered inside a container element
|
||||||
</div>
|
// 2. centered mode - the modal is rendered in the middle of the screen
|
||||||
);
|
// The mode is determined by the presence of the `container` prop and by whether the available space is large enough to fit the modal.
|
||||||
|
|
||||||
type ModalType = React.FC<{ children: React.ReactNode }> & {
|
// Necessary for container mode - need to reposition the modal on scroll and resize events.
|
||||||
|
useWindowSize();
|
||||||
|
useWindowScroll();
|
||||||
|
|
||||||
|
let position:
|
||||||
|
| {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
maxWidth: number;
|
||||||
|
maxHeight: number;
|
||||||
|
transform: string;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
const { clientWidth: screenWidth, clientHeight: screenHeight } =
|
||||||
|
document.documentElement;
|
||||||
|
if (container) {
|
||||||
|
const rect = container?.getBoundingClientRect();
|
||||||
|
|
||||||
|
// If available space in `visibleRect` is smaller than these, fallback to positioning in the middle of the screen.
|
||||||
|
const minWidth = 384; // matches the w-96 below
|
||||||
|
const minHeight = 300;
|
||||||
|
const offset = 8;
|
||||||
|
|
||||||
|
const visibleRect = {
|
||||||
|
left: Math.max(rect.left, 0),
|
||||||
|
right: Math.min(rect.right, screenWidth),
|
||||||
|
top: Math.max(rect.top, 0),
|
||||||
|
bottom: Math.min(rect.bottom, screenHeight),
|
||||||
|
};
|
||||||
|
const maxWidth = visibleRect.right - visibleRect.left - 2 * offset;
|
||||||
|
const maxHeight = visibleRect.bottom - visibleRect.top - 2 * offset;
|
||||||
|
|
||||||
|
const center = {
|
||||||
|
left: visibleRect.left + (visibleRect.right - visibleRect.left) / 2,
|
||||||
|
top: visibleRect.top + (visibleRect.bottom - visibleRect.top) / 2,
|
||||||
|
};
|
||||||
|
position = {
|
||||||
|
left: center.left,
|
||||||
|
top: center.top,
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
maxWidth,
|
||||||
|
maxHeight,
|
||||||
|
};
|
||||||
|
if (maxWidth < minWidth || maxHeight < minHeight) {
|
||||||
|
position = undefined; // modal is hard to fit in the container, fallback to positioning it in the middle of the screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"bg-white rounded-md shadow-toast flex flex-col overflow-auto border w-96",
|
||||||
|
position ? "fixed" : null
|
||||||
|
)}
|
||||||
|
style={
|
||||||
|
position ?? {
|
||||||
|
maxHeight: "calc(100% - 20px)",
|
||||||
|
maxWidth: "calc(100% - 20px)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ModalType = React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
container?: HTMLElement; // if specified, modal will be positioned over the visible part of the container, if it's not too small
|
||||||
|
close: () => void;
|
||||||
|
}> & {
|
||||||
Body: typeof ModalBody;
|
Body: typeof ModalBody;
|
||||||
Footer: typeof ModalFooter;
|
Footer: typeof ModalFooter;
|
||||||
Header: typeof ModalHeader;
|
Header: typeof ModalHeader;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Modal: ModalType = ({ children }) => {
|
export const Modal: ModalType = ({ children, container, close }) => {
|
||||||
const [el] = React.useState(() => document.createElement("div"));
|
const [el] = React.useState(() => document.createElement("div"));
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -68,15 +153,19 @@ export const Modal: ModalType = ({ children }) => {
|
||||||
}, [el]);
|
}, [el]);
|
||||||
|
|
||||||
const modal = (
|
const modal = (
|
||||||
<div className="squiggle">
|
<SquiggleContainer>
|
||||||
<div className="fixed inset-0 z-40 flex justify-center items-center">
|
<ModalContext.Provider value={{ close }}>
|
||||||
<Overlay />
|
<div className="squiggle">
|
||||||
<ModalWindow>{children}</ModalWindow>
|
<div className="fixed inset-0 z-40 flex justify-center items-center">
|
||||||
</div>
|
<Overlay />
|
||||||
</div>
|
<ModalWindow container={container}>{children}</ModalWindow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalContext.Provider>
|
||||||
|
</SquiggleContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
return ReactDOM.createPortal(modal, el);
|
return ReactDOM.createPortal(modal, container || el);
|
||||||
};
|
};
|
||||||
|
|
||||||
Modal.Body = ModalBody;
|
Modal.Body = ModalBody;
|
||||||
|
|
|
@ -4825,7 +4825,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^18.0.9":
|
"@types/react@*", "@types/react@^18.0.1", "@types/react@^18.0.9":
|
||||||
version "18.0.15"
|
version "18.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe"
|
||||||
integrity sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==
|
integrity sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==
|
||||||
|
@ -15261,7 +15261,7 @@ react-vega@^7.6.0:
|
||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
vega-embed "^6.5.1"
|
vega-embed "^6.5.1"
|
||||||
|
|
||||||
react@^18.1.0:
|
react@^18.0.0, react@^18.1.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||||
|
|
Loading…
Reference in New Issue
Block a user