diff --git a/README.md b/README.md index db82723..555a4f1 100644 --- a/README.md +++ b/README.md @@ -135,3 +135,4 @@ onClick={(e) => { | `showSpinner` | `boolean` | Determines whether to accompany the loading bar with a spinner. Turned off by default. | `false` | | `ignoreSearchParams` | `boolean` | Determines whether to ignore search parameters in the URL when triggering the loader. Turned off by default. | `false` | | `dir` | `ltr` or `rtl` | Sets the direction of the top-loading bar. | `ltr` | +| `delay` | `number` | Specifies the delay in milliseconds before starting the progress bar. | `0` | diff --git a/src/__tests__/HolyLoader.test.ts b/src/__tests__/HolyLoader.test.ts new file mode 100644 index 0000000..1fde3f9 --- /dev/null +++ b/src/__tests__/HolyLoader.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it, vi } from 'vitest'; +import { render, fireEvent } from '@testing-library/react'; +import HolyLoader, { startHolyLoader, stopHolyLoader } from '../index'; + +describe('HolyLoader', () => { + it('should start the progress bar after the specified delay', () => { + vi.useFakeTimers(); + const { container } = render(); + const anchor = document.createElement('a'); + anchor.href = '/test'; + document.body.appendChild(anchor); + + fireEvent.click(anchor); + expect(container.querySelector('#holy-progress')).toBeNull(); + + vi.advanceTimersByTime(200); + expect(container.querySelector('#holy-progress')).not.toBeNull(); + + vi.useRealTimers(); + }); + + it('should not start the progress bar if navigation happens within the delay', () => { + vi.useFakeTimers(); + const { container } = render(); + const anchor = document.createElement('a'); + anchor.href = '/test'; + document.body.appendChild(anchor); + + fireEvent.click(anchor); + expect(container.querySelector('#holy-progress')).toBeNull(); + + fireEvent.click(anchor); + vi.advanceTimersByTime(200); + expect(container.querySelector('#holy-progress')).toBeNull(); + + vi.useRealTimers(); + }); + + it('should start the progress bar immediately if delay is 0', () => { + const { container } = render(); + const anchor = document.createElement('a'); + anchor.href = '/test'; + document.body.appendChild(anchor); + + fireEvent.click(anchor); + expect(container.querySelector('#holy-progress')).not.toBeNull(); + }); + + it('should manually start and stop the progress bar', () => { + const { container } = render(); + + startHolyLoader(); + expect(container.querySelector('#holy-progress')).not.toBeNull(); + + stopHolyLoader(); + expect(container.querySelector('#holy-progress')).toBeNull(); + }); +}); diff --git a/src/index.tsx b/src/index.tsx index 8d23653..2d2bd4b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -72,6 +72,12 @@ export interface HolyLoaderProps { * Default: "ltr" */ dir?: 'ltr' | 'rtl'; + + /** + * Specifies the delay in milliseconds before starting the progress bar. + * Default: 0 milliseconds + */ + delay?: number; } /** @@ -104,9 +110,11 @@ const HolyLoader = ({ boxShadow = DEFAULTS.boxShadow, showSpinner = DEFAULTS.showSpinner, ignoreSearchParams = DEFAULTS.ignoreSearchParams, - dir = DEFAULTS.dir, + dir = DEFAULTS.dir, + delay = 0, }: HolyLoaderProps): null => { const holyProgressRef = React.useRef(null); + const delayTimeoutRef = React.useRef(null); React.useEffect(() => { const startProgress = (): void => { @@ -212,7 +220,11 @@ const HolyLoader = ({ return; } - startProgress(); + if (delay > 0) { + delayTimeoutRef.current = setTimeout(startProgress, delay); + } else { + startProgress(); + } } catch (error) { stopProgress(); } @@ -229,7 +241,7 @@ const HolyLoader = ({ zIndex, boxShadow, showSpinner, - dir + dir, }); } @@ -243,8 +255,12 @@ const HolyLoader = ({ document.removeEventListener('click', handleClick); document.removeEventListener(START_HOLY_EVENT, startProgress); document.removeEventListener(STOP_HOLY_EVENT, stopProgress); + + if (delayTimeoutRef.current !== null) { + clearTimeout(delayTimeoutRef.current); + } }; - }, [holyProgressRef]); + }, [holyProgressRef, delay]); return null; };