diff --git a/.changeset/forty-tables-drive.md b/.changeset/forty-tables-drive.md
new file mode 100644
index 0000000..179ce61
--- /dev/null
+++ b/.changeset/forty-tables-drive.md
@@ -0,0 +1,5 @@
+---
+'@raddix/use-scroll-lock': major
+---
+
+Added useScrollLock hook.
diff --git a/.changeset/six-students-run.md b/.changeset/six-students-run.md
new file mode 100644
index 0000000..08bb74e
--- /dev/null
+++ b/.changeset/six-students-run.md
@@ -0,0 +1,5 @@
+---
+'@raddix/use-clipboard': minor
+---
+
+Remove @raddix/use-timeout of peerDependecies
diff --git a/.changeset/tasty-boats-tease.md b/.changeset/tasty-boats-tease.md
new file mode 100644
index 0000000..03e7601
--- /dev/null
+++ b/.changeset/tasty-boats-tease.md
@@ -0,0 +1,6 @@
+---
+'@raddix/use-scroll-position': minor
+'@raddix/use-window-size': minor
+---
+
+Remove @raddix/use-event-listener of peerDependencies
diff --git a/docs/_config.json b/docs/_config.json
index 2f9f8cf..6cb1e30 100644
--- a/docs/_config.json
+++ b/docs/_config.json
@@ -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"
@@ -64,6 +72,10 @@
"title": "useCounter",
"path": "/hooks/use-counter"
},
+ {
+ "title": "useCountDown",
+ "path": "/hooks/use-count-down"
+ },
{
"title": "useDebounce",
"path": "/hooks/use-debounce"
@@ -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"
- }
- ]
}
]
}
diff --git a/docs/en/use-scroll-lock.mdx b/docs/en/use-scroll-lock.mdx
new file mode 100644
index 0000000..178a7a1
--- /dev/null
+++ b/docs/en/use-scroll-lock.mdx
@@ -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.
+
+
+
+## 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 (
+
+ )
+}
+
+export default function App() {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+ <>
+ {isOpen && setIsOpen(false)} />}
+
+ >
+ );
+}
+```
+
+## API
+
+### Parameters
+
+The `useScrollLock` hook does not accept any parameters.
\ No newline at end of file
diff --git a/docs/es/use-scroll-lock.mdx b/docs/es/use-scroll-lock.mdx
new file mode 100644
index 0000000..95fbe3b
--- /dev/null
+++ b/docs/es/use-scroll-lock.mdx
@@ -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.
+
+
+
+## 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 (
+
+ )
+}
+
+export default function App() {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+ <>
+ {isOpen && setIsOpen(false)} />}
+
+ >
+ );
+}
+```
+
+## API
+
+### Parámetros
+
+El hook `useScrollLock` no acepta ningun parámetro.
\ No newline at end of file
diff --git a/packages/hooks/use-clipboard/package.json b/packages/hooks/use-clipboard/package.json
index 738614e..b811b7c 100644
--- a/packages/hooks/use-clipboard/package.json
+++ b/packages/hooks/use-clipboard/package.json
@@ -33,8 +33,7 @@
],
"peerDependencies": {
"react": ">=16.8.0",
- "react-dom": ">=16.8.0",
- "@raddix/use-timeout": "workspace:*"
+ "react-dom": ">=16.8.0"
},
"clean-package": "../../../clean-package.config.json",
"tsup": {
diff --git a/packages/hooks/use-clipboard/src/index.ts b/packages/hooks/use-clipboard/src/index.ts
index e83573a..08113d5 100644
--- a/packages/hooks/use-clipboard/src/index.ts
+++ b/packages/hooks/use-clipboard/src/index.ts
@@ -1,5 +1,4 @@
-import { useTimeout } from '@raddix/use-timeout';
-import { useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
export type UseClipboard = (options?: {
timeout?: number;
@@ -10,7 +9,7 @@ export type UseClipboard = (options?: {
export const useClipboard: UseClipboard = (options = {}) => {
const { timeout = 2000, onError, onSuccess } = options;
const [isCopied, setIsCopied] = useState(false);
- const { run } = useTimeout(() => setIsCopied(false), timeout, false);
+ const idTimeout = useRef(null);
const copy = (data: string) => {
if ('clipboard' in navigator) {
@@ -19,7 +18,7 @@ export const useClipboard: UseClipboard = (options = {}) => {
.then(() => {
setIsCopied(true);
onSuccess?.();
- run();
+ idTimeout.current = setTimeout(() => setIsCopied(false), timeout);
})
.catch(err => onError?.(err));
} else {
@@ -27,5 +26,11 @@ export const useClipboard: UseClipboard = (options = {}) => {
}
};
+ useEffect(() => {
+ return () => {
+ if (idTimeout.current) clearTimeout(idTimeout.current);
+ };
+ }, []);
+
return [isCopied, copy];
};
diff --git a/packages/hooks/use-scroll-lock/README.md b/packages/hooks/use-scroll-lock/README.md
new file mode 100644
index 0000000..82ab538
--- /dev/null
+++ b/packages/hooks/use-scroll-lock/README.md
@@ -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.
\ No newline at end of file
diff --git a/packages/hooks/use-scroll-lock/package.json b/packages/hooks/use-scroll-lock/package.json
new file mode 100644
index 0000000..69a1a86
--- /dev/null
+++ b/packages/hooks/use-scroll-lock/package.json
@@ -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 (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"
+ ]
+ }
+}
diff --git a/packages/hooks/use-scroll-lock/src/index.ts b/packages/hooks/use-scroll-lock/src/index.ts
new file mode 100644
index 0000000..b3645d2
--- /dev/null
+++ b/packages/hooks/use-scroll-lock/src/index.ts
@@ -0,0 +1,36 @@
+import { useLayoutEffect, useRef } from 'react';
+
+interface OriginalStyle {
+ overflow: string;
+ paddingRight: string;
+}
+
+export const useScrollLock = (): void => {
+ const originalStyle = useRef(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);
+ };
+ }, []);
+};
diff --git a/packages/hooks/use-scroll-lock/tests/index.test.ts b/packages/hooks/use-scroll-lock/tests/index.test.ts
new file mode 100644
index 0000000..5bb6dc7
--- /dev/null
+++ b/packages/hooks/use-scroll-lock/tests/index.test.ts
@@ -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)
+ );
+ });
+});
diff --git a/packages/hooks/use-scroll-position/package.json b/packages/hooks/use-scroll-position/package.json
index a08fe52..894e2a5 100644
--- a/packages/hooks/use-scroll-position/package.json
+++ b/packages/hooks/use-scroll-position/package.json
@@ -32,8 +32,7 @@
],
"peerDependencies": {
"react": ">=16.8.0",
- "react-dom": ">=16.8.0",
- "@raddix/use-event-listener": "workspace:*"
+ "react-dom": ">=16.8.0"
},
"clean-package": "../../../clean-package.config.json",
"tsup": {
diff --git a/packages/hooks/use-scroll-position/src/index.ts b/packages/hooks/use-scroll-position/src/index.ts
index e8a8928..1ea6d94 100644
--- a/packages/hooks/use-scroll-position/src/index.ts
+++ b/packages/hooks/use-scroll-position/src/index.ts
@@ -1,39 +1,35 @@
import { type RefObject, useEffect, useState } from 'react';
-import { useEventListener } from '@raddix/use-event-listener';
export interface ScrollPosition {
x: number | null;
y: number | null;
}
-export interface Options {
- target?: RefObject | Document;
+interface Options {
+ target?: RefObject;
}
-export const useScrollPosition = ({
- target = globalThis.document
-}: Options = {}): ScrollPosition => {
+export const useScrollPosition = ({ target }: Options = {}): ScrollPosition => {
const [scrollPosition, setScrollPosition] = useState({
x: 0,
y: 0
});
- const handle = () => {
- const targetElement =
- target instanceof Document ? document.documentElement : target.current;
-
- setScrollPosition({
- x: targetElement?.scrollLeft ?? null,
- y: targetElement?.scrollTop ?? null
- });
- };
-
useEffect(() => {
+ const element = target ? target.current : window;
+
+ const handle = () => {
+ setScrollPosition({
+ x: (target ? target.current?.scrollLeft : window.scrollX) ?? null,
+ y: (target ? target.current?.scrollTop : window.scrollY) ?? null
+ });
+ };
handle();
+
+ element?.addEventListener('scroll', handle, { passive: true });
+ return () => element?.removeEventListener('scroll', handle);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- useEventListener('scroll', handle, { target, passive: true });
-
return scrollPosition;
};
diff --git a/packages/hooks/use-scroll-position/tests/index.test.ts b/packages/hooks/use-scroll-position/tests/index.test.ts
index 8ca1d7a..cf27729 100644
--- a/packages/hooks/use-scroll-position/tests/index.test.ts
+++ b/packages/hooks/use-scroll-position/tests/index.test.ts
@@ -42,9 +42,17 @@ describe('useScrollPosition test:', () => {
expect(result.current).toEqual({ x: 0, y: 0 });
act(() => {
- document.documentElement.scrollTop = 100;
- document.documentElement.scrollLeft = 50;
- document.dispatchEvent(new Event('scroll'));
+ Object.defineProperty(window, 'scrollY', {
+ value: 100,
+ configurable: true
+ });
+
+ Object.defineProperty(window, 'scrollX', {
+ value: 50,
+ configurable: true
+ });
+
+ window.dispatchEvent(new Event('scroll'));
});
expect(result.current).toEqual({ x: 50, y: 100 });
diff --git a/packages/hooks/use-window-size/package.json b/packages/hooks/use-window-size/package.json
index ec2f7ba..072738f 100644
--- a/packages/hooks/use-window-size/package.json
+++ b/packages/hooks/use-window-size/package.json
@@ -32,8 +32,7 @@
],
"peerDependencies": {
"react": ">=16.8.0",
- "react-dom": ">=16.8.0",
- "@raddix/use-event-listener": "workspace:*"
+ "react-dom": ">=16.8.0"
},
"clean-package": "../../../clean-package.config.json",
"tsup": {
diff --git a/packages/hooks/use-window-size/src/index.ts b/packages/hooks/use-window-size/src/index.ts
index 4128253..8347a93 100644
--- a/packages/hooks/use-window-size/src/index.ts
+++ b/packages/hooks/use-window-size/src/index.ts
@@ -1,5 +1,4 @@
import { useEffect, useState } from 'react';
-import { useEventListener } from '@raddix/use-event-listener';
interface Size {
width: number;
@@ -21,9 +20,10 @@ export const useWindowSize = (): Size => {
useEffect(() => {
handleResize();
- }, []);
+ window.addEventListener('resize', handleResize);
- useEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, []);
return windowSize;
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index efd0ca5..fe2335d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -237,9 +237,6 @@ importers:
packages/hooks/use-clipboard:
dependencies:
- '@raddix/use-timeout':
- specifier: workspace:*
- version: link:../use-timeout
react:
specifier: '>=16.8.0'
version: 18.2.0
@@ -358,11 +355,17 @@ importers:
specifier: '>=16.8.0'
version: 18.2.0(react@18.2.0)
+ packages/hooks/use-scroll-lock:
+ dependencies:
+ react:
+ specifier: '>=16.8.0'
+ version: 18.2.0
+ react-dom:
+ specifier: '>=16.8.0'
+ version: 18.2.0(react@18.2.0)
+
packages/hooks/use-scroll-position:
dependencies:
- '@raddix/use-event-listener':
- specifier: workspace:*
- version: link:../use-event-listener
react:
specifier: '>=16.8.0'
version: 18.2.0
@@ -414,9 +417,6 @@ importers:
packages/hooks/use-window-size:
dependencies:
- '@raddix/use-event-listener':
- specifier: workspace:*
- version: link:../use-event-listener
react:
specifier: '>=16.8.0'
version: 18.2.0