Skip to content

Commit

Permalink
feat: add useScrollPosition hook (#80)
Browse files Browse the repository at this point in the history
A hook to listen to the scroll position of the current window or element.
  • Loading branch information
immois authored Dec 10, 2023
1 parent 5445202 commit 88b7e0d
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/yellow-experts-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@raddix/use-scroll-position': major
---

Added the useScrollPosition hook
5 changes: 5 additions & 0 deletions packages/hooks/use-scroll-position/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# useScrollPosition

The `useScrollPosition` hook listens for the scroll position of the current window or element.

Please refer to the [documentation](https://www.raddix.website/docs/use-scroll-position) for more information.
47 changes: 47 additions & 0 deletions packages/hooks/use-scroll-position/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@raddix/use-scroll-position",
"description": "A hook to listen to the scroll position of the current window or element.",
"version": "0.1.0",
"license": "MIT",
"main": "src/index.ts",
"author": "Moises Machuca Valverde <rolan.machuca@gmail.com> (https://www.moisesmachuca.com)",
"homepage": "https://www.raddix.website",
"repository": {
"type": "git",
"url": "https://github.com/gdvu/raddix.git"
},
"keywords": [
"react-hook",
"react-scroll-position-hook",
"react-use-scroll-position",
"use-scroll-position",
"use-scroll-position-hook",
"hook-scroll-position"
],
"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",
"@raddix/use-event-listener": "workspace:*"
},
"clean-package": "../../../clean-package.config.json",
"tsup": {
"clean": true,
"target": "es2019",
"format": [
"cjs",
"esm"
]
}
}
40 changes: 40 additions & 0 deletions packages/hooks/use-scroll-position/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { type RefObject, useEffect, useState } from 'react';
import { useEventListener, _document } from '@raddix/use-event-listener';

export interface ScrollPosition {
x: number | null;
y: number | null;
}

export interface Options<E extends HTMLElement> {
target?: RefObject<E> | Document | null;
}

export const useScroll = <E extends HTMLElement = HTMLDivElement>({
target = _document
}: Options<E> = {}): ScrollPosition => {
const [scrollPosition, setScrollPosition] = useState<ScrollPosition>({
x: 0,
y: 0
});

const handle = () => {
if (!target) return;
const targetElement =
target instanceof Document ? document.documentElement : target.current;

setScrollPosition({
x: targetElement?.scrollLeft ?? null,
y: targetElement?.scrollTop ?? null
});
};

useEffect(() => {
handle();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEventListener(target, 'scroll', handle, { passive: true });

return scrollPosition;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { renderHook, act } from '@testing-library/react';
import { useScroll } from '../src';

describe('useScroll test:', () => {
let container: HTMLDivElement;

beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});

afterEach(() => {
document.body.removeChild(container);
jest.clearAllMocks();
});

it('should initialize scroll position to (0, 0)', () => {
const { result } = renderHook(() => useScroll());

expect(result.current).toEqual({ x: 0, y: 0 });
});

it('should update scroll position on scroll event', () => {
const { result } = renderHook(() =>
useScroll({ target: { current: container } })
);

expect(result.current).toEqual({ x: 0, y: 0 });

act(() => {
container.scrollTop = 100;
container.scrollLeft = 50;
container.dispatchEvent(new Event('scroll'));
});

expect(result.current).toEqual({ x: 50, y: 100 });
});

it('should update scroll position on document scroll event', () => {
const { result } = renderHook(() => useScroll());

expect(result.current).toEqual({ x: 0, y: 0 });

act(() => {
document.documentElement.scrollTop = 100;
document.documentElement.scrollLeft = 50;
document.dispatchEvent(new Event('scroll'));
});

expect(result.current).toEqual({ x: 50, y: 100 });
});

it('should not update scroll position if target is null', () => {
const { result } = renderHook(() => useScroll({ target: null }));

expect(result.current).toEqual({ x: 0, y: 0 });

act(() => {
document.documentElement.scrollTop = 100;
document.documentElement.scrollLeft = 50;
document.dispatchEvent(new Event('scroll'));
});

expect(result.current).toEqual({ x: 0, y: 0 });
});

it('should not update the scroll position if the element does not exist', () => {
const { result } = renderHook(() =>
useScroll({ target: { current: null } })
);

expect(result.current).toEqual({ x: null, y: null });
});
});
12 changes: 12 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 88b7e0d

Please sign in to comment.