Lock scrolling body when Dialog is open

This commit is contained in:
Justin Edmund 2023-01-15 10:36:38 -08:00
parent b4f217071f
commit 1bb16db205
4 changed files with 81 additions and 0 deletions

View file

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import * as DialogPrimitive from '@radix-ui/react-dialog' import * as DialogPrimitive from '@radix-ui/react-dialog'
import { useLockedBody } from 'usehooks-ts'
import './index.scss' import './index.scss'
@ -18,9 +19,12 @@ export const Dialog = React.forwardRef<HTMLDivElement, Props>(function dialog(
) { ) {
const [locked, setLocked] = useLockedBody(false, 'root') const [locked, setLocked] = useLockedBody(false, 'root')
function toggleLocked(open: boolean) {
setLocked(open)
} }
function onOpenChange(open: boolean) { function onOpenChange(open: boolean) {
toggleLocked(open)
props.onOpenChange(open) props.onOpenChange(open)
} }

56
hooks/useLockedBody.tsx Normal file
View file

@ -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

20
package-lock.json generated
View file

@ -40,6 +40,7 @@
"react-string-replace": "^1.1.0", "react-string-replace": "^1.1.0",
"sanitize-html": "^2.8.1", "sanitize-html": "^2.8.1",
"sass": "^1.49.0", "sass": "^1.49.0",
"usehooks-ts": "^2.9.1",
"valtio": "^1.3.0", "valtio": "^1.3.0",
"youtube-api-v3-wrapper": "^2.3.0" "youtube-api-v3-wrapper": "^2.3.0"
}, },
@ -7136,6 +7137,19 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0" "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": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -12203,6 +12217,12 @@
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"requires": {} "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": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View file

@ -45,6 +45,7 @@
"react-string-replace": "^1.1.0", "react-string-replace": "^1.1.0",
"sanitize-html": "^2.8.1", "sanitize-html": "^2.8.1",
"sass": "^1.49.0", "sass": "^1.49.0",
"usehooks-ts": "^2.9.1",
"valtio": "^1.3.0", "valtio": "^1.3.0",
"youtube-api-v3-wrapper": "^2.3.0" "youtube-api-v3-wrapper": "^2.3.0"
}, },