react-top-progress 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +163 -0
- package/dist/index.mjs +134 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# react-top-progress
|
|
2
|
+
|
|
3
|
+
A lightweight, modern, and performant top loading progress bar for React.
|
|
4
|
+
|
|
5
|
+
Perfect for Next.js App Router, Vite, and standard single-page applications.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **0 dependencies**: Tiny footprint (~1kb gzipped).
|
|
10
|
+
- **Realistic loading feel**: Increments incrementally like real network requests.
|
|
11
|
+
- **Premium DX**: Controlled programmatically with simple `startProgress` / `finishProgress` API.
|
|
12
|
+
- **Gradient & Glow Support**: Fully customizable colors, shadow pegs, and effects.
|
|
13
|
+
- **Performant**: Uses `transform` hardware acceleration and `cubic-bezier` transitions.
|
|
14
|
+
- **TypeScript ready**: Full types included.
|
|
15
|
+
- **React 18+ & Next.js ready**: Contains `"use client"` where necessary.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install react-top-progress
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Or using yarn / pnpm / bun:**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
yarn add react-top-progress
|
|
27
|
+
pnpm add react-top-progress
|
|
28
|
+
bun add react-top-progress
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### 1. Add the component to your root layout/app
|
|
34
|
+
|
|
35
|
+
At the top level of your application (like Next.js `app/layout.tsx` or Vite `src/App.tsx`), render the `<TopProgress />` component.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { TopProgress } from "react-top-progress";
|
|
39
|
+
|
|
40
|
+
export default function RootLayout({ children }) {
|
|
41
|
+
return (
|
|
42
|
+
<html lang="en">
|
|
43
|
+
<body>
|
|
44
|
+
<TopProgress />
|
|
45
|
+
{children}
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Control it programmatically
|
|
53
|
+
|
|
54
|
+
Import the API from anywhere in your app to trigger the progress bar manually!
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { startProgress, finishProgress } from "react-top-progress";
|
|
58
|
+
|
|
59
|
+
async function handleAction() {
|
|
60
|
+
startProgress();
|
|
61
|
+
try {
|
|
62
|
+
await someHeavyTask();
|
|
63
|
+
} finally {
|
|
64
|
+
finishProgress();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. The `withProgress` Utility
|
|
70
|
+
|
|
71
|
+
For an even better Developer Experience, simply wrap your promises securely with `withProgress`!
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { withProgress } from "react-top-progress";
|
|
75
|
+
|
|
76
|
+
async function handleFetch() {
|
|
77
|
+
const data = await withProgress(fetch("/api/data"));
|
|
78
|
+
console.log(data);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Customization API
|
|
83
|
+
|
|
84
|
+
The `TopProgress` component accepts the following props:
|
|
85
|
+
|
|
86
|
+
| Prop | Type | Default | Description |
|
|
87
|
+
| ----------- | --------- | ----------- | ------------------------------------------------------------------------------------------------------------ |
|
|
88
|
+
| `color` | `string` | `"#29D"` | Flat hex color or `linear-gradient(...)` function string |
|
|
89
|
+
| `height` | `number` | `3` | Height of the progress bar in pixels |
|
|
90
|
+
| `showGlow` | `boolean` | `true` | Whether to display the premium box-shadow drop-glow and trailing peg |
|
|
91
|
+
| `glowColor` | `string` | `undefined` | Optionally override the color of the glow effect. By default, it infers realistically from your main `color` |
|
|
92
|
+
| `zIndex` | `number` | `9999` | The z-index stacking context level. |
|
|
93
|
+
|
|
94
|
+
## Credits & Inspiration
|
|
95
|
+
|
|
96
|
+
Designed for the modern web. Inspired by the classic `nprogress` and Next.js' `nextjs-toploader`. Ideal when you need explicit control without relying solely on framework routing events.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface TopProgressProps {
|
|
4
|
+
color?: string;
|
|
5
|
+
height?: number;
|
|
6
|
+
showGlow?: boolean;
|
|
7
|
+
glowColor?: string;
|
|
8
|
+
zIndex?: number;
|
|
9
|
+
}
|
|
10
|
+
declare const TopProgress: ({ color, height, showGlow, glowColor, zIndex, }: TopProgressProps) => react_jsx_runtime.JSX.Element | null;
|
|
11
|
+
|
|
12
|
+
declare const startProgress: () => void;
|
|
13
|
+
declare const finishProgress: () => void;
|
|
14
|
+
declare const withProgress: <T>(promise: Promise<T>) => Promise<T>;
|
|
15
|
+
|
|
16
|
+
export { TopProgress, finishProgress, startProgress, withProgress };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface TopProgressProps {
|
|
4
|
+
color?: string;
|
|
5
|
+
height?: number;
|
|
6
|
+
showGlow?: boolean;
|
|
7
|
+
glowColor?: string;
|
|
8
|
+
zIndex?: number;
|
|
9
|
+
}
|
|
10
|
+
declare const TopProgress: ({ color, height, showGlow, glowColor, zIndex, }: TopProgressProps) => react_jsx_runtime.JSX.Element | null;
|
|
11
|
+
|
|
12
|
+
declare const startProgress: () => void;
|
|
13
|
+
declare const finishProgress: () => void;
|
|
14
|
+
declare const withProgress: <T>(promise: Promise<T>) => Promise<T>;
|
|
15
|
+
|
|
16
|
+
export { TopProgress, finishProgress, startProgress, withProgress };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
TopProgress: () => TopProgress,
|
|
25
|
+
finishProgress: () => finishProgress,
|
|
26
|
+
startProgress: () => startProgress,
|
|
27
|
+
withProgress: () => withProgress
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/TopProgress.tsx
|
|
32
|
+
var import_react = require("react");
|
|
33
|
+
|
|
34
|
+
// src/progressStore.ts
|
|
35
|
+
var ProgressStore = class {
|
|
36
|
+
listeners = /* @__PURE__ */ new Set();
|
|
37
|
+
value = null;
|
|
38
|
+
interval = null;
|
|
39
|
+
hideTimer = null;
|
|
40
|
+
subscribe(listener) {
|
|
41
|
+
this.listeners.add(listener);
|
|
42
|
+
listener(this.value);
|
|
43
|
+
return () => {
|
|
44
|
+
this.listeners.delete(listener);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
notify() {
|
|
48
|
+
this.listeners.forEach((listener) => listener(this.value));
|
|
49
|
+
}
|
|
50
|
+
start() {
|
|
51
|
+
if (this.value !== null) return;
|
|
52
|
+
this.cleanup();
|
|
53
|
+
this.value = 10;
|
|
54
|
+
this.notify();
|
|
55
|
+
this.interval = setInterval(() => {
|
|
56
|
+
if (this.value !== null) {
|
|
57
|
+
const amount = Math.max(0.5, (95 - this.value) / 10);
|
|
58
|
+
this.value = Math.min(this.value + amount, 95);
|
|
59
|
+
this.notify();
|
|
60
|
+
}
|
|
61
|
+
}, 200);
|
|
62
|
+
}
|
|
63
|
+
finish() {
|
|
64
|
+
this.cleanup();
|
|
65
|
+
this.value = 100;
|
|
66
|
+
this.notify();
|
|
67
|
+
this.hideTimer = setTimeout(() => {
|
|
68
|
+
this.value = null;
|
|
69
|
+
this.notify();
|
|
70
|
+
}, 400);
|
|
71
|
+
}
|
|
72
|
+
cleanup() {
|
|
73
|
+
if (this.interval) clearInterval(this.interval);
|
|
74
|
+
if (this.hideTimer) clearTimeout(this.hideTimer);
|
|
75
|
+
}
|
|
76
|
+
getValue() {
|
|
77
|
+
return this.value;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var progressStore = new ProgressStore();
|
|
81
|
+
var useProgressStore = () => progressStore;
|
|
82
|
+
var startProgress = () => progressStore.start();
|
|
83
|
+
var finishProgress = () => progressStore.finish();
|
|
84
|
+
var withProgress = async (promise) => {
|
|
85
|
+
startProgress();
|
|
86
|
+
try {
|
|
87
|
+
const result = await promise;
|
|
88
|
+
finishProgress();
|
|
89
|
+
return result;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
finishProgress();
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/TopProgress.tsx
|
|
97
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
98
|
+
var TopProgress = ({
|
|
99
|
+
color = "#29D",
|
|
100
|
+
// Still the default flat color, but can now accept "linear-gradient(...)"
|
|
101
|
+
height = 3,
|
|
102
|
+
showGlow = true,
|
|
103
|
+
glowColor,
|
|
104
|
+
zIndex = 9999
|
|
105
|
+
}) => {
|
|
106
|
+
const store = useProgressStore();
|
|
107
|
+
const [progress, setProgress] = (0, import_react.useState)(store.getValue());
|
|
108
|
+
(0, import_react.useEffect)(() => {
|
|
109
|
+
return store.subscribe(setProgress);
|
|
110
|
+
}, [store]);
|
|
111
|
+
if (progress === null) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const isGradient = color.includes("gradient(");
|
|
115
|
+
const backgroundStyle = isGradient ? { backgroundImage: color } : { backgroundColor: color };
|
|
116
|
+
const computedGlowColor = glowColor || (isGradient ? "rgba(41, 216, 255, 0.4)" : color);
|
|
117
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
118
|
+
"div",
|
|
119
|
+
{
|
|
120
|
+
style: {
|
|
121
|
+
position: "fixed",
|
|
122
|
+
top: 0,
|
|
123
|
+
left: 0,
|
|
124
|
+
width: `${progress}%`,
|
|
125
|
+
height: `${height}px`,
|
|
126
|
+
...backgroundStyle,
|
|
127
|
+
// Box shadow generates a beautiful diffused glow underneath the progress bar
|
|
128
|
+
boxShadow: showGlow ? `0 0 10px ${computedGlowColor}, 0 0 5px ${computedGlowColor}` : "none",
|
|
129
|
+
// Smooth and performant 60fps CSS transitions
|
|
130
|
+
transition: "width 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease",
|
|
131
|
+
opacity: progress === 100 ? 0 : 1,
|
|
132
|
+
zIndex,
|
|
133
|
+
// Force hardware acceleration for paint performance
|
|
134
|
+
transform: "translateZ(0)",
|
|
135
|
+
// Don't intercept clicks
|
|
136
|
+
pointerEvents: "none"
|
|
137
|
+
},
|
|
138
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
139
|
+
"div",
|
|
140
|
+
{
|
|
141
|
+
style: {
|
|
142
|
+
display: showGlow ? "block" : "none",
|
|
143
|
+
position: "absolute",
|
|
144
|
+
right: 0,
|
|
145
|
+
top: 0,
|
|
146
|
+
width: 100,
|
|
147
|
+
height: "100%",
|
|
148
|
+
boxShadow: `0 0 10px ${computedGlowColor}, 0 0 5px ${computedGlowColor}`,
|
|
149
|
+
opacity: 1,
|
|
150
|
+
transform: "rotate(3deg) translate(0px, -4px)"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
158
|
+
0 && (module.exports = {
|
|
159
|
+
TopProgress,
|
|
160
|
+
finishProgress,
|
|
161
|
+
startProgress,
|
|
162
|
+
withProgress
|
|
163
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/TopProgress.tsx
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
|
|
6
|
+
// src/progressStore.ts
|
|
7
|
+
var ProgressStore = class {
|
|
8
|
+
listeners = /* @__PURE__ */ new Set();
|
|
9
|
+
value = null;
|
|
10
|
+
interval = null;
|
|
11
|
+
hideTimer = null;
|
|
12
|
+
subscribe(listener) {
|
|
13
|
+
this.listeners.add(listener);
|
|
14
|
+
listener(this.value);
|
|
15
|
+
return () => {
|
|
16
|
+
this.listeners.delete(listener);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
notify() {
|
|
20
|
+
this.listeners.forEach((listener) => listener(this.value));
|
|
21
|
+
}
|
|
22
|
+
start() {
|
|
23
|
+
if (this.value !== null) return;
|
|
24
|
+
this.cleanup();
|
|
25
|
+
this.value = 10;
|
|
26
|
+
this.notify();
|
|
27
|
+
this.interval = setInterval(() => {
|
|
28
|
+
if (this.value !== null) {
|
|
29
|
+
const amount = Math.max(0.5, (95 - this.value) / 10);
|
|
30
|
+
this.value = Math.min(this.value + amount, 95);
|
|
31
|
+
this.notify();
|
|
32
|
+
}
|
|
33
|
+
}, 200);
|
|
34
|
+
}
|
|
35
|
+
finish() {
|
|
36
|
+
this.cleanup();
|
|
37
|
+
this.value = 100;
|
|
38
|
+
this.notify();
|
|
39
|
+
this.hideTimer = setTimeout(() => {
|
|
40
|
+
this.value = null;
|
|
41
|
+
this.notify();
|
|
42
|
+
}, 400);
|
|
43
|
+
}
|
|
44
|
+
cleanup() {
|
|
45
|
+
if (this.interval) clearInterval(this.interval);
|
|
46
|
+
if (this.hideTimer) clearTimeout(this.hideTimer);
|
|
47
|
+
}
|
|
48
|
+
getValue() {
|
|
49
|
+
return this.value;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var progressStore = new ProgressStore();
|
|
53
|
+
var useProgressStore = () => progressStore;
|
|
54
|
+
var startProgress = () => progressStore.start();
|
|
55
|
+
var finishProgress = () => progressStore.finish();
|
|
56
|
+
var withProgress = async (promise) => {
|
|
57
|
+
startProgress();
|
|
58
|
+
try {
|
|
59
|
+
const result = await promise;
|
|
60
|
+
finishProgress();
|
|
61
|
+
return result;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
finishProgress();
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/TopProgress.tsx
|
|
69
|
+
import { jsx } from "react/jsx-runtime";
|
|
70
|
+
var TopProgress = ({
|
|
71
|
+
color = "#29D",
|
|
72
|
+
// Still the default flat color, but can now accept "linear-gradient(...)"
|
|
73
|
+
height = 3,
|
|
74
|
+
showGlow = true,
|
|
75
|
+
glowColor,
|
|
76
|
+
zIndex = 9999
|
|
77
|
+
}) => {
|
|
78
|
+
const store = useProgressStore();
|
|
79
|
+
const [progress, setProgress] = useState(store.getValue());
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
return store.subscribe(setProgress);
|
|
82
|
+
}, [store]);
|
|
83
|
+
if (progress === null) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const isGradient = color.includes("gradient(");
|
|
87
|
+
const backgroundStyle = isGradient ? { backgroundImage: color } : { backgroundColor: color };
|
|
88
|
+
const computedGlowColor = glowColor || (isGradient ? "rgba(41, 216, 255, 0.4)" : color);
|
|
89
|
+
return /* @__PURE__ */ jsx(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
style: {
|
|
93
|
+
position: "fixed",
|
|
94
|
+
top: 0,
|
|
95
|
+
left: 0,
|
|
96
|
+
width: `${progress}%`,
|
|
97
|
+
height: `${height}px`,
|
|
98
|
+
...backgroundStyle,
|
|
99
|
+
// Box shadow generates a beautiful diffused glow underneath the progress bar
|
|
100
|
+
boxShadow: showGlow ? `0 0 10px ${computedGlowColor}, 0 0 5px ${computedGlowColor}` : "none",
|
|
101
|
+
// Smooth and performant 60fps CSS transitions
|
|
102
|
+
transition: "width 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease",
|
|
103
|
+
opacity: progress === 100 ? 0 : 1,
|
|
104
|
+
zIndex,
|
|
105
|
+
// Force hardware acceleration for paint performance
|
|
106
|
+
transform: "translateZ(0)",
|
|
107
|
+
// Don't intercept clicks
|
|
108
|
+
pointerEvents: "none"
|
|
109
|
+
},
|
|
110
|
+
children: /* @__PURE__ */ jsx(
|
|
111
|
+
"div",
|
|
112
|
+
{
|
|
113
|
+
style: {
|
|
114
|
+
display: showGlow ? "block" : "none",
|
|
115
|
+
position: "absolute",
|
|
116
|
+
right: 0,
|
|
117
|
+
top: 0,
|
|
118
|
+
width: 100,
|
|
119
|
+
height: "100%",
|
|
120
|
+
boxShadow: `0 0 10px ${computedGlowColor}, 0 0 5px ${computedGlowColor}`,
|
|
121
|
+
opacity: 1,
|
|
122
|
+
transform: "rotate(3deg) translate(0px, -4px)"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
export {
|
|
130
|
+
TopProgress,
|
|
131
|
+
finishProgress,
|
|
132
|
+
startProgress,
|
|
133
|
+
withProgress
|
|
134
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-top-progress",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight modern top loading progress bar for React",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react",
|
|
13
|
+
"progress",
|
|
14
|
+
"loader",
|
|
15
|
+
"top-loader",
|
|
16
|
+
"route-progress"
|
|
17
|
+
],
|
|
18
|
+
"author": "Harry Mate",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"react": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/react": "^19.2.14",
|
|
28
|
+
"tsup": "^8.5.1",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
}
|
|
31
|
+
}
|