From 41c6af7b13362071c13a61b35d42fce990cb8cbc Mon Sep 17 00:00:00 2001 From: Martin Petrov Date: Wed, 18 Oct 2023 23:07:14 +0300 Subject: [PATCH 1/4] Add useScroll Hook --- src/hooks/index.ts | 1 + src/hooks/useScroll/index.ts | 1 + src/hooks/useScroll/useScroll.stories.tsx | 0 src/hooks/useScroll/useScroll.test.tsx | 47 ++++++++++++++++++ src/hooks/useScroll/useScroll.ts | 60 +++++++++++++++++++++++ 5 files changed, 109 insertions(+) create mode 100644 src/hooks/useScroll/index.ts create mode 100644 src/hooks/useScroll/useScroll.stories.tsx create mode 100644 src/hooks/useScroll/useScroll.test.tsx create mode 100644 src/hooks/useScroll/useScroll.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 64380446..cb978a11 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -17,4 +17,5 @@ export { default as useMemoization } from "./useMemoization"; 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 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..e69de29b diff --git a/src/hooks/useScroll/useScroll.test.tsx b/src/hooks/useScroll/useScroll.test.tsx new file mode 100644 index 00000000..e07c49aa --- /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.scrollPosition).toEqual({ x: 0, y: 0 }); + expect(result.current.scrollDirection).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..ed4f85bf --- /dev/null +++ b/src/hooks/useScroll/useScroll.ts @@ -0,0 +1,60 @@ +import { useState, useEffect } from "react"; + +const useScroll = () => { + const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 }); + const [scrollDirection, setScrollDirection] = useState("none"); + + const scrollToTop = () => { + window.scrollTo({ top: 0, behavior: "smooth" }); + }; + + const scrollToBottom = () => { + window.scrollTo({ top: document.body.scrollHeight, 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.offsetTop; + window.scrollTo({ + top: offsetTop, + behavior: "smooth", + }); + } + }; + + useEffect(() => { + const handleScroll = () => { + const { pageYOffset, pageXOffset } = window; + setScrollPosition({ x: pageXOffset, y: pageYOffset }); + + if (pageYOffset > scrollPosition.y) { + setScrollDirection("down"); + } else if (pageYOffset < scrollPosition.y) { + setScrollDirection("up"); + } + }; + + window.addEventListener("scroll", handleScroll); + + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, [scrollPosition]); + + return { + scrollPosition, + scrollDirection, + scrollToTop, + scrollToBottom, + scrollToPosition, + scrollToId, + }; +}; + +export default useScroll; From 8b5ce1bd872c0d5b7a51b921691ac91d9f07d127 Mon Sep 17 00:00:00 2001 From: Martin Petrov Date: Thu, 19 Oct 2023 01:26:34 +0300 Subject: [PATCH 2/4] Update useScroll Story --- src/hooks/useScroll/useScroll.stories.tsx | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/hooks/useScroll/useScroll.stories.tsx b/src/hooks/useScroll/useScroll.stories.tsx index e69de29b..de1fcd63 100644 --- a/src/hooks/useScroll/useScroll.stories.tsx +++ b/src/hooks/useScroll/useScroll.stories.tsx @@ -0,0 +1,40 @@ +import { useState } from "react"; +import { Flex, Text, Button } from "../../components"; +import useScroll from "./useScroll"; +import { fr } from "../../utils"; + +export default { + title: "useScroll", + component: useScroll, +}; + +export const Default = () => { + const { + scrollToId, + scrollDirection, + scrollPosition, + scrollToBottom, + scrollToPosition, + scrollToTop, + } = useScroll(); + + return ( + + + + + + + Position: {JSON.stringify(scrollPosition)} + Direction: {scrollDirection} + + + Element With Id + + + ); +}; From 5eaed897019e8bdff8fc00ef98325b96076bdf90 Mon Sep 17 00:00:00 2001 From: Martin Petrov Date: Thu, 19 Oct 2023 23:31:26 +0300 Subject: [PATCH 3/4] Add callback support to hook --- src/hooks/useScroll/useScroll.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hooks/useScroll/useScroll.ts b/src/hooks/useScroll/useScroll.ts index ed4f85bf..1112cad7 100644 --- a/src/hooks/useScroll/useScroll.ts +++ b/src/hooks/useScroll/useScroll.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -const useScroll = () => { +const useScroll = (cb: (e: Event) => void = (e: Event) => {}) => { const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 }); const [scrollDirection, setScrollDirection] = useState("none"); @@ -29,7 +29,9 @@ const useScroll = () => { }; useEffect(() => { - const handleScroll = () => { + const handleScroll = (e: Event) => { + cb(e); + const { pageYOffset, pageXOffset } = window; setScrollPosition({ x: pageXOffset, y: pageYOffset }); From a149524b089631c0338a96d161f0f679bc4d9bdc Mon Sep 17 00:00:00 2001 From: Martin Petrov Date: Sun, 29 Oct 2023 12:36:47 +0200 Subject: [PATCH 4/4] Add scrollToRight, scrollToLeft And Fix The Story --- src/hooks/useScroll/useScroll.stories.tsx | 42 ++++++++++-------- src/hooks/useScroll/useScroll.test.tsx | 4 +- src/hooks/useScroll/useScroll.ts | 54 +++++++++++++++++------ 3 files changed, 65 insertions(+), 35 deletions(-) diff --git a/src/hooks/useScroll/useScroll.stories.tsx b/src/hooks/useScroll/useScroll.stories.tsx index de1fcd63..d7dba308 100644 --- a/src/hooks/useScroll/useScroll.stories.tsx +++ b/src/hooks/useScroll/useScroll.stories.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Flex, Text, Button } from "../../components"; +import { Flex, Text, Button, Stack } from "../../components"; import useScroll from "./useScroll"; import { fr } from "../../utils"; @@ -10,31 +10,35 @@ export default { export const Default = () => { const { - scrollToId, - scrollDirection, - scrollPosition, + direction, + position, + scrollToTop, scrollToBottom, + scrollToLeft, + scrollToRight, scrollToPosition, - scrollToTop, + scrollToId, } = useScroll(); return ( - - - - - - + + + + - Position: {JSON.stringify(scrollPosition)} - Direction: {scrollDirection} + + Position: {JSON.stringify(position)} + Direction: {direction} - - Element With Id + + + Element with ID + - + ); }; diff --git a/src/hooks/useScroll/useScroll.test.tsx b/src/hooks/useScroll/useScroll.test.tsx index e07c49aa..e053ca85 100644 --- a/src/hooks/useScroll/useScroll.test.tsx +++ b/src/hooks/useScroll/useScroll.test.tsx @@ -5,8 +5,8 @@ describe("useScroll", () => { it("should initialize with default values", () => { const { result } = renderHook(() => useScroll()); - expect(result.current.scrollPosition).toEqual({ x: 0, y: 0 }); - expect(result.current.scrollDirection).toBe("none"); + expect(result.current.position).toEqual({ x: 0, y: 0 }); + expect(result.current.direction).toBe("none"); }); it("should scroll to top", () => { diff --git a/src/hooks/useScroll/useScroll.ts b/src/hooks/useScroll/useScroll.ts index 1112cad7..3089519e 100644 --- a/src/hooks/useScroll/useScroll.ts +++ b/src/hooks/useScroll/useScroll.ts @@ -1,8 +1,15 @@ import { useState, useEffect } from "react"; -const useScroll = (cb: (e: Event) => void = (e: Event) => {}) => { - const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 }); - const [scrollDirection, setScrollDirection] = useState("none"); +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" }); @@ -12,6 +19,17 @@ const useScroll = (cb: (e: Event) => void = (e: Event) => {}) => { 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" }); }; @@ -20,7 +38,7 @@ const useScroll = (cb: (e: Event) => void = (e: Event) => {}) => { const element = document.getElementById(elementId); if (element) { - const offsetTop = element.offsetTop; + const offsetTop = element.getBoundingClientRect().top + window.scrollY; window.scrollTo({ top: offsetTop, behavior: "smooth", @@ -30,16 +48,22 @@ const useScroll = (cb: (e: Event) => void = (e: Event) => {}) => { useEffect(() => { const handleScroll = (e: Event) => { - cb(e); - const { pageYOffset, pageXOffset } = window; - setScrollPosition({ x: pageXOffset, y: pageYOffset }); - if (pageYOffset > scrollPosition.y) { - setScrollDirection("down"); - } else if (pageYOffset < scrollPosition.y) { - setScrollDirection("up"); + 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); @@ -47,13 +71,15 @@ const useScroll = (cb: (e: Event) => void = (e: Event) => {}) => { return () => { window.removeEventListener("scroll", handleScroll); }; - }, [scrollPosition]); + }, [position]); return { - scrollPosition, - scrollDirection, + position, + direction, scrollToTop, scrollToBottom, + scrollToLeft, + scrollToRight, scrollToPosition, scrollToId, };