Skip to content

Commit

Permalink
Add useScroll Hook #84 from prismaneui/add-use-scroll-hook
Browse files Browse the repository at this point in the history
Add useScroll Hook
  • Loading branch information
spleafy authored Oct 29, 2023
2 parents cb8e85c + 27cb5b3 commit 99ef37f
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
1 change: 1 addition & 0 deletions src/hooks/useScroll/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./useScroll";
44 changes: 44 additions & 0 deletions src/hooks/useScroll/useScroll.stories.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Stack pos="relative" h="100vh" w="100vw">
<Flex gap={fr(2)} pos="fixed" l={fr(5)} r={fr(5)}>
<Button onClick={() => scrollToTop()}>Scroll to Top</Button>
<Button onClick={() => scrollToBottom()}>Scroll to Bottom</Button>
<Button onClick={() => scrollToLeft()}>Scroll to Left</Button>
<Button onClick={() => scrollToRight()}>Scroll to Right</Button>
<Button onClick={() => scrollToPosition(20, 500)}>
Scroll to Position
</Button>
<Button onClick={() => scrollToId("elementId")}>Scroll to ID</Button>
<Text>Position: {JSON.stringify(position)}</Text>
<Text>Direction: {direction}</Text>
</Flex>
<Flex w="150vw" h="200vh">
<Flex id="elementId" my={fr(240)}>
Element with ID
</Flex>
</Flex>
</Stack>
);
};
47 changes: 47 additions & 0 deletions src/hooks/useScroll/useScroll.test.tsx
Original file line number Diff line number Diff line change
@@ -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");
});
});
});
88 changes: 88 additions & 0 deletions src/hooks/useScroll/useScroll.ts
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 99ef37f

Please sign in to comment.