Skip to content

Commit e357edf

Browse files
authored
Enable client-side nav detection
1 parent 9531b9e commit e357edf

File tree

1 file changed

+106
-83
lines changed

1 file changed

+106
-83
lines changed

src/index.tsx

Lines changed: 106 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint-disable max-len */
2+
/* eslint-disable @typescript-eslint/no-explicit-any */
13
/**
24
*
35
* NextTopLoader
@@ -10,7 +12,7 @@
1012
import * as PropTypes from 'prop-types';
1113
import * as React from 'react';
1214
import * as NProgress from 'nprogress';
13-
export interface NextTopLoaderProps {
15+
export type NextTopLoaderProps = {
1416
/**
1517
* Color for the TopLoader.
1618
* @default "#29d"
@@ -27,7 +29,7 @@ export interface NextTopLoaderProps {
2729
*/
2830
crawlSpeed?: number;
2931
/**
30-
* The height for the TopLoader.
32+
* The height for the TopLoader in pixels (px).
3133
* @default 3
3234
*/
3335
height?: number;
@@ -53,99 +55,120 @@ export interface NextTopLoaderProps {
5355
speed?: number;
5456
}
5557

56-
const NextTopLoader = (props: NextTopLoaderProps) => {
57-
const color = '#29d';
58-
const height = 3;
58+
const NextTopLoader = ({
59+
color,
60+
height,
61+
showSpinner,
62+
crawl,
63+
crawlSpeed,
64+
initialPosition,
65+
easing,
66+
speed,
67+
}: NextTopLoaderProps) => {
68+
const defaultColor = '#29d';
69+
const defaultHeight = 3;
5970

6071
const styles = (
6172
<style>
62-
{`#nprogress{pointer-events:none}#nprogress .bar{background:${
63-
props.color ? props.color : color
64-
};position:fixed;z-index:1031;top:0;left:0;width:100%;height:${
65-
props.height ? props.height : height
66-
}px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px ${
67-
props.color ? props.color : color
68-
},0 0 5px ${
69-
props.color ? props.color : color
70-
};opacity:1;-webkit-transform:rotate(3deg) translate(0px,-4px);-ms-transform:rotate(3deg) translate(0px,-4px);transform:rotate(3deg) translate(0px,-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:${
71-
props.color ? props.color : color
72-
};border-left-color:${
73-
props.color ? props.color : color
74-
};border-radius:50%;-webkit-animation:nprogress-spinner 400ms linear infinite;animation:nprogress-spinner 400ms linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}`}
73+
{
74+
`#nprogress{pointer-events:none}#nprogress .bar{background:${
75+
color ?? defaultColor
76+
};position:fixed;z-index:1031;top:0;left:0;width:100%;height:${
77+
height ?? defaultHeight
78+
}px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px ${
79+
color ?? defaultColor
80+
},0 0 5px ${
81+
color ?? defaultColor
82+
};opacity:1;-webkit-transform:rotate(3deg) translate(0px,-4px);-ms-transform:rotate(3deg) translate(0px,-4px);transform:rotate(3deg) translate(0px,-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:${
83+
color ?? defaultColor
84+
};border-left-color:${
85+
color ?? defaultColor
86+
};border-radius:50%;-webkit-animation:nprogress-spinner 400ms linear infinite;animation:nprogress-spinner 400ms linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}`
87+
}
7588
</style>
7689
);
7790

7891
React.useEffect(() => {
79-
if (props.showSpinner !== undefined) {
80-
NProgress.configure({ showSpinner: props.showSpinner });
81-
}
82-
if (props.crawl !== undefined) {
83-
NProgress.configure({ trickle: props.crawl });
84-
}
85-
if (props.crawlSpeed !== undefined) {
86-
NProgress.configure({ trickleSpeed: props.crawlSpeed });
87-
}
88-
if (props.initialPosition !== undefined) {
89-
NProgress.configure({ minimum: props.initialPosition });
90-
}
91-
if (props.easing !== undefined) {
92-
NProgress.configure({ easing: props.easing });
93-
}
94-
if (props.speed !== undefined) {
95-
NProgress.configure({ speed: props.speed });
92+
NProgress.configure({
93+
showSpinner: showSpinner ?? true,
94+
trickle: crawl ?? true,
95+
trickleSpeed: crawlSpeed ?? 200,
96+
minimum: initialPosition ?? 0.08,
97+
easing: easing ?? 'ease',
98+
speed: speed ?? 200,
99+
});
100+
101+
function isAnchorOfCurrentUrl(currentUrl: string, newUrl: string) {
102+
const currentUrlObj = new URL(currentUrl);
103+
const newUrlObj = new URL(newUrl);
104+
// Compare hostname, pathname, and search parameters
105+
if (
106+
currentUrlObj.hostname === newUrlObj.hostname &&
107+
currentUrlObj.pathname === newUrlObj.pathname &&
108+
currentUrlObj.search === newUrlObj.search
109+
) {
110+
// Check if the new URL is just an anchor of the current URL page
111+
const currentHash = currentUrlObj.hash;
112+
const newHash = newUrlObj.hash;
113+
return (
114+
currentHash !== newHash && currentUrlObj.href.replace(currentHash, '') === newUrlObj.href.replace(newHash, '')
115+
);
116+
}
117+
return false;
96118
}
119+
97120
// eslint-disable-next-line no-var
98121
var npgclass = document.querySelectorAll('html');
99-
let navLinks = document.querySelectorAll('a');
100-
navLinks.forEach((navLink) => {
101-
navLink.addEventListener('click', (event: MouseEvent) => {
102-
let currentUrl = window.location.href;
103-
let newUrl = (event.currentTarget as HTMLAnchorElement).href;
104-
let isExternalLink = (event.currentTarget as HTMLAnchorElement).target === "_blank";
105-
function isAnchorOfCurrentUrl(currentUrl: string, newUrl: string) {
106-
const currentUrlObj = new URL(currentUrl);
107-
const newUrlObj = new URL(newUrl);
108-
// Compare hostname, pathname, and search parameters
109-
if (
110-
currentUrlObj.hostname === newUrlObj.hostname &&
111-
currentUrlObj.pathname === newUrlObj.pathname &&
112-
currentUrlObj.search === newUrlObj.search
113-
) {
114-
// Check if the new URL is just an anchor of the current URL page
115-
const currentHash = currentUrlObj.hash;
116-
const newHash = newUrlObj.hash;
117-
return (
118-
currentHash !== newHash &&
119-
currentUrlObj.href.replace(currentHash, '') === newUrlObj.href.replace(newHash, '')
120-
);
122+
function findClosestAnchor(element: HTMLElement | null): HTMLAnchorElement | null {
123+
while (element && element.tagName.toLowerCase() !== 'a') {
124+
element = element.parentElement;
125+
}
126+
return element as HTMLAnchorElement;
127+
}
128+
function handleClick(event: MouseEvent) {
129+
try {
130+
const target = event.target as HTMLElement;
131+
const anchor = findClosestAnchor(target);
132+
if (anchor) {
133+
const currentUrl = window.location.href;
134+
const newUrl = (anchor as HTMLAnchorElement).href;
135+
const isExternalLink = (anchor as HTMLAnchorElement).target === "_blank";
136+
const isAnchor = isAnchorOfCurrentUrl(currentUrl, newUrl);
137+
if (newUrl === currentUrl || isAnchor || isExternalLink) {
138+
NProgress.start();
139+
NProgress.done();
140+
[].forEach.call(npgclass, function (el: Element) {
141+
el.classList.remove("nprogress-busy");
142+
});
143+
} else {
144+
NProgress.start();
145+
(function (history) {
146+
const pushState = history.pushState;
147+
history.pushState = function () {
148+
NProgress.done();
149+
[].forEach.call(npgclass, function (el: Element) {
150+
el.classList.remove("nprogress-busy");
151+
});
152+
return pushState.apply(history, arguments as any);
153+
};
154+
})(window.history);
121155
}
122-
return false;
123-
}
124-
const isAnchor = isAnchorOfCurrentUrl(currentUrl, newUrl);
125-
if (newUrl === currentUrl || isAnchor || isExternalLink) {
126-
NProgress.start();
127-
NProgress.done();
128-
[].forEach.call(npgclass, function (el: Element) {
129-
el.classList.remove('nprogress-busy');
130-
});
131-
} else {
132-
NProgress.start();
133-
(function (history) {
134-
// eslint-disable-next-line no-var
135-
var pushState = history.pushState;
136-
history.pushState = function () {
137-
NProgress.done();
138-
[].forEach.call(npgclass, function (el: Element) {
139-
el.classList.remove('nprogress-busy');
140-
});
141-
// eslint-disable-next-line prefer-rest-params
142-
return pushState.apply(history, arguments);
143-
};
144-
})(window.history);
145156
}
146-
});
147-
});
148-
});
157+
} catch (err) {
158+
console.log('NextTopLoader error: ', err);
159+
}
160+
}
161+
162+
// Add the global click event listener
163+
document.addEventListener("click", handleClick);
164+
165+
// Clean up the global click event listener when the component is unmounted
166+
return () => {
167+
document.removeEventListener("click", handleClick);
168+
};
169+
170+
// eslint-disable-next-line react-hooks/exhaustive-deps
171+
}, []);
149172

150173
return styles;
151174
};

0 commit comments

Comments
 (0)