From 1bb16db205be6bd7d878e8350d4acf22310194f7 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sun, 15 Jan 2023 10:36:38 -0800 Subject: [PATCH] Lock scrolling body when Dialog is open --- components/Dialog/index.tsx | 4 +++ hooks/useLockedBody.tsx | 56 +++++++++++++++++++++++++++++++++++++ package-lock.json | 20 +++++++++++++ package.json | 1 + 4 files changed, 81 insertions(+) create mode 100644 hooks/useLockedBody.tsx diff --git a/components/Dialog/index.tsx b/components/Dialog/index.tsx index 36a3dc25..b5824236 100644 --- a/components/Dialog/index.tsx +++ b/components/Dialog/index.tsx @@ -1,5 +1,6 @@ import React from 'react' import * as DialogPrimitive from '@radix-ui/react-dialog' +import { useLockedBody } from 'usehooks-ts' import './index.scss' @@ -18,9 +19,12 @@ export const Dialog = React.forwardRef(function dialog( ) { const [locked, setLocked] = useLockedBody(false, 'root') + function toggleLocked(open: boolean) { + setLocked(open) } function onOpenChange(open: boolean) { + toggleLocked(open) props.onOpenChange(open) } diff --git a/hooks/useLockedBody.tsx b/hooks/useLockedBody.tsx new file mode 100644 index 00000000..227938e6 --- /dev/null +++ b/hooks/useLockedBody.tsx @@ -0,0 +1,56 @@ +// https://usehooks-ts.com/react-hook/use-locked-body + +import { useEffect, useState } from 'react' +import { useIsomorphicLayoutEffect } from 'usehooks-ts' + +type UseLockedBodyOutput = [boolean, (locked: boolean) => void] + +function useLockedBody( + initialLocked = false, + rootId = '___gatsby' // Default to `___gatsby` to not introduce breaking change +): UseLockedBodyOutput { + const [locked, setLocked] = useState(initialLocked) + + // Do the side effect before render + useIsomorphicLayoutEffect(() => { + if (!locked) { + return + } + + // Save initial body style + const originalOverflow = document.body.style.overflow + const originalPaddingRight = document.body.style.paddingRight + + // Lock body scroll + document.body.style.overflow = 'hidden' + + // Get the scrollBar width + const root = document.getElementById(rootId) // or root + const scrollBarWidth = root ? root.offsetWidth - root.scrollWidth : 0 + + // Avoid width reflow + if (scrollBarWidth) { + document.body.style.paddingRight = `${scrollBarWidth}px` + } + + return () => { + document.body.style.overflow = originalOverflow + + if (scrollBarWidth) { + document.body.style.paddingRight = originalPaddingRight + } + } + }, [locked]) + + // Update state if initialValue changes + useEffect(() => { + if (locked !== initialLocked) { + setLocked(initialLocked) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [initialLocked]) + + return [locked, setLocked] +} + +export default useLockedBody diff --git a/package-lock.json b/package-lock.json index d0c42f68..520ded8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "react-string-replace": "^1.1.0", "sanitize-html": "^2.8.1", "sass": "^1.49.0", + "usehooks-ts": "^2.9.1", "valtio": "^1.3.0", "youtube-api-v3-wrapper": "^2.3.0" }, @@ -7136,6 +7137,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/usehooks-ts": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz", + "integrity": "sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==", + "engines": { + "node": ">=16.15.0", + "npm": ">=8" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12203,6 +12217,12 @@ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "requires": {} }, + "usehooks-ts": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz", + "integrity": "sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 77715f3f..4771dae9 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "react-string-replace": "^1.1.0", "sanitize-html": "^2.8.1", "sass": "^1.49.0", + "usehooks-ts": "^2.9.1", "valtio": "^1.3.0", "youtube-api-v3-wrapper": "^2.3.0" },