diff --git a/src/hooks/index.ts b/src/hooks/index.ts index ae65a34d..06ee85d3 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -19,6 +19,7 @@ export { default as useNetworkStatus } from "./useNetworkStatus"; export { default as useOutsideClick } from "./useOutsideClick"; export { default as usePresence } from "./usePresence"; export { default as usePrevious } from "./usePrevious"; +export { default as useScroll } from "./useScroll"; export { default as useSorting } from "./useSorting"; export { default as useSearch } from "./useSearch"; export { default as useToggle } from "./useToggle"; diff --git a/src/hooks/useScroll/index.ts b/src/hooks/useScroll/index.ts new file mode 100644 index 00000000..9bc33ab1 --- /dev/null +++ b/src/hooks/useScroll/index.ts @@ -0,0 +1 @@ +export { default } from "./useScroll"; diff --git a/src/hooks/useScroll/useScroll.stories.tsx b/src/hooks/useScroll/useScroll.stories.tsx new file mode 100644 index 00000000..d7dba308 --- /dev/null +++ b/src/hooks/useScroll/useScroll.stories.tsx @@ -0,0 +1,44 @@ +import { useState } from "react"; +import { Flex, Text, Button, Stack } from "../../components"; +import useScroll from "./useScroll"; +import { fr } from "../../utils"; + +export default { + title: "useScroll", + component: useScroll, +}; + +export const Default = () => { + const { + direction, + position, + scrollToTop, + scrollToBottom, + scrollToLeft, + scrollToRight, + scrollToPosition, + scrollToId, + } = useScroll(); + + return ( + + + + + + + + + Position: {JSON.stringify(position)} + Direction: {direction} + + + + Element with ID + + + + ); +}; diff --git a/src/hooks/useScroll/useScroll.test.tsx b/src/hooks/useScroll/useScroll.test.tsx new file mode 100644 index 00000000..e053ca85 --- /dev/null +++ b/src/hooks/useScroll/useScroll.test.tsx @@ -0,0 +1,47 @@ +import { renderHook, act } from "@testing-library/react-hooks"; +import useScroll from "./useScroll"; + +describe("useScroll", () => { + it("should initialize with default values", () => { + const { result } = renderHook(() => useScroll()); + + expect(result.current.position).toEqual({ x: 0, y: 0 }); + expect(result.current.direction).toBe("none"); + }); + + it("should scroll to top", () => { + const { result } = renderHook(() => useScroll()); + + act(() => { + result.current.scrollToTop(); + }); + }); + + it("should scroll to bottom", () => { + const { result } = renderHook(() => useScroll()); + + act(() => { + result.current.scrollToBottom(); + }); + }); + + it("should scroll to a specific position", () => { + const { result } = renderHook(() => useScroll()); + + act(() => { + result.current.scrollToPosition(100, 200); + }); + }); + + it("should scroll to an element by id", () => { + const targetElement = document.createElement("div"); + targetElement.id = "targetElement"; + document.body.appendChild(targetElement); + + const { result } = renderHook(() => useScroll()); + + act(() => { + result.current.scrollToId("targetElement"); + }); + }); +}); diff --git a/src/hooks/useScroll/useScroll.ts b/src/hooks/useScroll/useScroll.ts new file mode 100644 index 00000000..3089519e --- /dev/null +++ b/src/hooks/useScroll/useScroll.ts @@ -0,0 +1,88 @@ +import { useState, useEffect } from "react"; + +const useScroll = ( + cb: ( + position: { x: number; y: number }, + direction: "up" | "down" | "left" | "right" | "none" + ) => void = () => {} +) => { + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [direction, setDirection] = useState< + "up" | "down" | "left" | "right" | "none" + >("none"); + + const scrollToTop = () => { + window.scrollTo({ top: 0, behavior: "smooth" }); + }; + + const scrollToBottom = () => { + window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" }); + }; + + const scrollToLeft = () => { + window.scrollTo({ left: 0, behavior: "smooth" }); + }; + + const scrollToRight = () => { + window.scrollTo({ + left: document.body.scrollWidth - window.innerWidth, + behavior: "smooth", + }); + }; + + const scrollToPosition = (x: number, y: number) => { + window.scrollTo({ top: y, left: x, behavior: "smooth" }); + }; + + const scrollToId = (elementId: string) => { + const element = document.getElementById(elementId); + + if (element) { + const offsetTop = element.getBoundingClientRect().top + window.scrollY; + window.scrollTo({ + top: offsetTop, + behavior: "smooth", + }); + } + }; + + useEffect(() => { + const handleScroll = (e: Event) => { + const { pageYOffset, pageXOffset } = window; + + if (pageYOffset > position.y) { + setDirection("down"); + } else if (pageYOffset < position.y) { + setDirection("up"); + } else if (pageXOffset > position.x) { + setDirection("right"); + } else if (pageXOffset < position.x) { + setDirection("left"); + } else { + setDirection("none"); + } + + setPosition({ x: pageXOffset, y: pageYOffset }); + cb({ x: pageXOffset, y: pageYOffset }, direction); + }; + + window.addEventListener("scroll", handleScroll); + + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, [position]); + + return { + position, + direction, + scrollToTop, + scrollToBottom, + scrollToLeft, + scrollToRight, + scrollToPosition, + scrollToId, + }; +}; + +export default useScroll;