Lock scrolling body when Dialog is open
This commit is contained in:
parent
b4f217071f
commit
1bb16db205
4 changed files with 81 additions and 0 deletions
|
|
@ -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
56
hooks/useLockedBody.tsx
Normal 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
20
package-lock.json
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue