Skip to content

Instantly share code, notes, and snippets.

@airtonix
Last active February 4, 2023 04:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save airtonix/c8c9af146185646e7451faa0f2ac96b7 to your computer and use it in GitHub Desktop.
Save airtonix/c8c9af146185646e7451faa0f2ac96b7 to your computer and use it in GitHub Desktop.
modal scroll lock that doesn't cause layout reflow or jitter.
import type { PropsWithChildren } from "react";
import { useBlanket } from "./useBlanket";
export function Blanket({ children }: PropsWithChildren) {
const { isOpen, setIsOpen, zIndex, color } = useBlanket();
return (
<>
{isOpen && (
<div
style={{
position: "fixed",
backgroundColor: color,
top: 0,
height: "100vh",
width: "100vw",
zIndex: zIndex,
}}
onClick={() => setIsOpen(false)}
/>
)}
{children}
</>
);
}
import { createContext } from "react";
type BlanketContextShape = {
isOpen?: boolean;
zIndex: number;
color: string;
setIsOpen: (yesno?: boolean) => void;
};
export const BlanketContext = createContext<BlanketContextShape>({
zIndex: 500,
color: `rgba(0,0,0,0.5)`,
setIsOpen() {
return;
},
});
import type { PropsWithChildren } from "react";
import { useCallback, useState } from "react";
import { BlanketContext } from "./BlanketContext";
export function BlanketProvider({
children,
color,
zIndex,
}: PropsWithChildren<{
zIndex?: number;
color?: string;
}>) {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [top, setTop] = useState(0);
const handleSetIsOpen = useCallback(
(yesno?: boolean) => {
if (typeof window === "undefined") return;
if (yesno) {
const scrollTop = window.scrollY;
document.body.style.top = `-${scrollTop}px`;
setTop(scrollTop);
}
if (window.innerHeight < document.body.scrollHeight) {
document.body.style.overflowY = (!!yesno && "scroll") || "auto";
document.body.style.position = (!!yesno && "fixed") || "static";
}
window.scrollTo({ top });
setIsOpen(() => !!yesno);
},
[top]
);
return (
<BlanketContext.Provider
value={{
isOpen,
setIsOpen: handleSetIsOpen,
color: color || `rgba(0,0,0,0.5)`,
zIndex: zIndex || 200,
}}
>
{children}
</BlanketContext.Provider>
);
}
export { Blanket } from "./Blanket";
export { BlanketProvider } from "./BlanketProvider";
export { useBlanket } from "./useBlanket";
import { useContext } from "react";
import { BlanketContext } from "./BlanketContext";
export function useBlanket() {
const context = useContext(BlanketContext);
if (!context)
throw new Error(
"useBlanket can only be used within children of BlanketProvider"
);
return context;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment