diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 8e04375..d448b34 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -57,6 +57,7 @@ export { useRenderCount } from './useRenderCount/useRenderCount'; export { useRenderInfo } from './useRenderInfo/useRenderInfo'; export { useRerender } from './useRerender/useRerender'; export { useScript } from './useScript/useScript'; +export { useScrollTo } from './useScrollTo/useScrollTo'; export { useSessionStorage } from './useSessionStorage/useSessionStorage'; export { useSet } from './useSet/useSet'; export { useShare } from './useShare/useShare'; diff --git a/src/hooks/useScrollTo/useScrollTo.demo.tsx b/src/hooks/useScrollTo/useScrollTo.demo.tsx new file mode 100644 index 0000000..6e40a61 --- /dev/null +++ b/src/hooks/useScrollTo/useScrollTo.demo.tsx @@ -0,0 +1,31 @@ +import { useScrollTo } from "@/hooks/useScrollTo/useScrollTo"; + +const blockStyle = { + border: '1px solid gray', + height: 300, + width: 300, +} + +const Demo = () => { + const {targetToScroll, scrollToTarget} = useScrollTo(); + + const handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + scrollToTarget(); + }; + + + return ( + <> + +
+
Block 1
+
Block 2
+
Block 3
+
+ + ) +} + +export default Demo diff --git a/src/hooks/useScrollTo/useScrollTo.test.ts b/src/hooks/useScrollTo/useScrollTo.test.ts new file mode 100644 index 0000000..3e5b0c5 --- /dev/null +++ b/src/hooks/useScrollTo/useScrollTo.test.ts @@ -0,0 +1,47 @@ +import { act, renderHook } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { useScrollTo } from './useScrollTo'; + +describe('useScrollTo', () => { + it('should define scrollTo and targetRef', () => { + const { result } = renderHook(() => useScrollTo()); + + expect(result.current.scrollToTarget).toBeDefined(); + expect(result.current.targetToScroll).toBeDefined(); + }); + + it('should scroll to target element', () => { + const { result } = renderHook(() => useScrollTo()); + const scrollIntoViewMock = vi.fn(); + + const mockElement = document.createElement('div'); + mockElement.scrollIntoView = scrollIntoViewMock; + + act(() => { + (result.current.targetToScroll as React.MutableRefObject).current = + mockElement; + }); + + act(() => { + result.current.scrollToTarget(); + }); + + expect(scrollIntoViewMock).toHaveBeenCalledWith({ + behavior: 'smooth', + block: 'nearest', + inline: 'nearest' + }); + }); + + it('should not scroll if targetRef is not set', () => { + const { result } = renderHook(() => useScrollTo()); + const scrollIntoViewMock = vi.fn(); + + act(() => { + result.current.scrollToTarget(); + }); + + expect(scrollIntoViewMock).not.toHaveBeenCalled(); + }); +}); diff --git a/src/hooks/useScrollTo/useScrollTo.ts b/src/hooks/useScrollTo/useScrollTo.ts new file mode 100644 index 0000000..ae1e3f5 --- /dev/null +++ b/src/hooks/useScrollTo/useScrollTo.ts @@ -0,0 +1,56 @@ +import { useRef } from 'react'; + +const OPTIONS_DEFAULT: ScrollIntoViewOptions = { + /** + * Defines the transition animation. + * One of 'auto' or 'smooth'. + * + * @default 'smooth' + */ + behavior: 'smooth', + /** + * Defines vertical alignment. + * One of `'start'`, `'center'`, `'end'`, or `'nearest'` + * + * @default 'nearest' + */ + block: 'nearest', + /** + * Defines horizontal alignment. + * One of `start`, `center`, `end`, or `nearest`. Defaults to nearest. + * + * @default 'nearest' + */ + inline: 'nearest' +}; + +interface UseScrollToReturn { + targetToScroll: React.RefObject; + scrollToTarget: () => void; +} + +/** + * @name useScrollTo + * @description Hook that provides a function to smoothly scroll to a target element. + * + * @param {ScrollIntoViewOptions} [options=OPTIONS_DEFAULT] - Options for the scrollIntoView method. + * + * @returns {Object} An object containing the reference to the target element and the function to scroll to it. + * @returns {React.RefObject} targetToScroll - The ref object to be attached to the element you want to scroll to. + * @returns {function} scrollToTarget - The function to call to scroll to the target element. + * + * @example + * const { targetToScroll, scrollToTarget } = useScrollTo(); + */ +export const useScrollTo = ( + options: ScrollIntoViewOptions = OPTIONS_DEFAULT +): UseScrollToReturn => { + const targetToScroll = useRef(null); + + const scrollToTarget = () => { + if (!targetToScroll.current) return; + targetToScroll.current.scrollIntoView(options); + }; + + return { targetToScroll, scrollToTarget }; +};