Skip to content

Commit

Permalink
feat: add useScrollLock (#104)
Browse files Browse the repository at this point in the history
A hook that disables scrolling in the document body.
  • Loading branch information
immois authored Aug 4, 2024
1 parent 0aee7d1 commit 74ab5a8
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/forty-tables-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@raddix/use-scroll-lock': major
---

Added useScrollLock hook.
26 changes: 12 additions & 14 deletions docs/_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,18 @@
"title": "useMediaQuery",
"path": "/hooks/use-media-query"
},
{
"title": "useScrollLock",
"path": "/hooks/use-scroll-lock"
},
{
"title": "useScrollPosition",
"path": "/hooks/use-scroll-position"
},
{
"title": "useScrollSpy",
"path": "/hooks/use-scroll-spy"
},
{
"title": "useWindowSize",
"path": "/hooks/use-window-size"
Expand Down Expand Up @@ -64,6 +72,10 @@
"title": "useCounter",
"path": "/hooks/use-counter"
},
{
"title": "useCountDown",
"path": "/hooks/use-count-down"
},
{
"title": "useDebounce",
"path": "/hooks/use-debounce"
Expand Down Expand Up @@ -103,20 +115,6 @@
"path": "/hooks/use-isomorphic-effect"
}
]
},
{
"title": "Utilities",
"heading": true,
"children": [
{
"title": "useCountDown",
"path": "/hooks/use-count-down"
},
{
"title": "useScrollSpy",
"path": "/hooks/use-scroll-spy"
}
]
}
]
}
56 changes: 56 additions & 0 deletions docs/en/use-scroll-lock.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: useScrollLock
description: Disables scrolling in the document body.
---

## Features

- Removes the scroll bar from the document, while preserving the width of the page.
- Works on any desktop or mobile browser.

## Installation

Install the custom hook from your command line.

<Snippet pkg text='@raddix/use-scroll-lock' />

## Usage

Once the component using the `useScrollLock` hook is mounted, scrolling is disabled
in the document body. When the component is unmounted, the hook returns a cleanup function
that restores the original overflow style.

```jsx
import { useState } from 'react';
import { useScrollLock } from '@raddix/use-scroll-lock';

function Modal({ handleClose }) {
useScrollLock();

return (
<dialog>
<button onClick={handleClose}>Close</button>
<h2>Modal</h2>
</dialog>
)
}

export default function App() {
const [isOpen, setIsOpen] = useState(false);

return (
<>
{isOpen && <Modal handleClose={() => setIsOpen(false)} />}
<button className="primary" onClick={() => setIsOpen(true)}>
Open Modal
</button>
</>
);
}
```

## API

### Parameters

The `useScrollLock` hook does not accept any parameters.
56 changes: 56 additions & 0 deletions docs/es/use-scroll-lock.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: useScrollLock
description: Deshabilita el desplazamiento en el cuerpo del documento.
---

## Características

- Elimina la barra de desplazamiento del documento, conservando el ancho de la página.
- Funciona en cualquier navegador de escritorio o móvil.

## Instalación

Instala el custom hook desde su linea de comando.

<Snippet pkg text='@raddix/use-scroll-lock' />

## Uso

Una vez que el componente que utiliza el hook `useScrollLock` se monta, se deshabilita el desplazamiento
en el cuerpo del documento. Cuando se desmonta el componente el hook devuelve una función de limpieza
que restaura el estilo de desbordamiento original.

```jsx
import { useState } from 'react';
import { useScrollLock } from '@raddix/use-scroll-lock';

function Modal({ handleClose }) {
useScrollLock();

return (
<dialog>
<button onClick={handleClose}>Close</button>
<h2>Modal</h2>
</dialog>
)
}

export default function App() {
const [isOpen, setIsOpen] = useState(false);

return (
<>
{isOpen && <Modal handleClose={() => setIsOpen(false)} />}
<button className="primary" onClick={() => setIsOpen(true)}>
Open Modal
</button>
</>
);
}
```

## API

### Parámetros

El hook `useScrollLock` no acepta ningun parámetro.
5 changes: 5 additions & 0 deletions packages/hooks/use-scroll-lock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# useScrollLock

A hook that locks and unlocks scroll.

Please refer to the [documentation](https://raddix.dev/hooks/use-scroll-lock) for more information.
46 changes: 46 additions & 0 deletions packages/hooks/use-scroll-lock/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "@raddix/use-scroll-lock",
"description": "A hook that locks and unlocks scroll.",
"version": "0.1.0",
"license": "MIT",
"main": "src/index.ts",
"author": "Moises Machuca Valverde <rolan.machuca@gmail.com> (https://www.moisesmachuca.com)",
"homepage": "https://raddix.dev",
"repository": {
"type": "git",
"url": "https://github.com/gdvu/raddix.git"
},
"keywords": [
"react-hook",
"react-scroll-lock-hook",
"react-use-scroll-lock",
"use-scroll-lock",
"use-scroll-lock-hook",
"hook-scroll-lock"
],
"sideEffects": false,
"scripts": {
"lint": "eslint \"{src,tests}/*.{ts,tsx,css}\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
"build": "tsup src --dts",
"prepack": "clean-package",
"postpack": "clean-package restore"
},
"files": [
"dist",
"README.md"
],
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"clean-package": "../../../clean-package.config.json",
"tsup": {
"clean": true,
"target": "es2019",
"format": [
"cjs",
"esm"
]
}
}
36 changes: 36 additions & 0 deletions packages/hooks/use-scroll-lock/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useLayoutEffect, useRef } from 'react';

interface OriginalStyle {
overflow: string;
paddingRight: string;
}

export const useScrollLock = (): void => {
const originalStyle = useRef<OriginalStyle | null>(null);

const preventTouch = (e: Event) => {
e.preventDefault();
return false;
};

useLayoutEffect(() => {
const scrollbarWidth = window.innerWidth - document.body.scrollWidth;
const paddingRight = window.getComputedStyle(document.body).paddingRight;
const overflow = window.getComputedStyle(document.body).overflow;
const right = scrollbarWidth + parseInt(paddingRight, 10);

originalStyle.current = { overflow, paddingRight };
document.body.style.paddingRight = `${right}px`;
document.body.style.overflow = 'hidden';
document.addEventListener('touchmove', preventTouch, { passive: false });

return () => {
if (originalStyle.current) {
document.body.style.overflow = originalStyle.current.overflow;
document.body.style.paddingRight = originalStyle.current.paddingRight;
}

document.removeEventListener('touchmove', preventTouch);
};
}, []);
};
44 changes: 44 additions & 0 deletions packages/hooks/use-scroll-lock/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable @typescript-eslint/unbound-method */
import { renderHook } from '@testing-library/react';
import { useScrollLock } from '../src';

describe('useScrollLock test:', () => {
beforeEach(() => {
document.body.style.overflow = 'auto';
document.body.style.paddingRight = '0px';

jest.spyOn(document, 'addEventListener');
jest.spyOn(document, 'removeEventListener');
});

afterEach(() => {
jest.restoreAllMocks();
});

it('should block the scroll and add the touchmove event', () => {
const { unmount } = renderHook(() => useScrollLock());
const touchMoveEvent = new Event('touchmove');
const scrollbarWidth = window.innerWidth - document.body.scrollWidth;
touchMoveEvent.preventDefault = jest.fn();
document.dispatchEvent(touchMoveEvent);

expect(touchMoveEvent.preventDefault).toHaveBeenCalled();
expect(document.body.style.overflow).toBe('hidden');
expect(document.body.style.paddingRight).toBe(`${scrollbarWidth}px`);

expect(document.addEventListener).toHaveBeenCalledWith(
'touchmove',
expect.any(Function),
{ passive: false }
);

unmount();
expect(document.body.style.overflow).toBe('auto');
expect(document.body.style.paddingRight).toBe('0px');

expect(document.removeEventListener).toHaveBeenCalledWith(
'touchmove',
expect.any(Function)
);
});
});
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 74ab5a8

Please sign in to comment.