q-top-progress-bar 1.0.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 +67 -0
- package/dist/index.d.mts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +103 -0
- package/dist/index.mjs +78 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# q-top-progress-bar
|
|
2
|
+
|
|
3
|
+
A slim, lightweight progress bar that appears at the very top of the viewport whenever a React Router navigation is in flight. Inspired by Shopify Admin and YouTube.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install q-top-progress-bar
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
*Note: `react`, `react-dom`, and `react-router` are required peer dependencies.*
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Simply drop `<TopProgressBar />` once in your root layout (e.g. `app/root.tsx` or wherever your router is provided).
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { TopProgressBar } from "q-top-progress-bar";
|
|
19
|
+
|
|
20
|
+
export default function RootLayout() {
|
|
21
|
+
return (
|
|
22
|
+
<html>
|
|
23
|
+
<body>
|
|
24
|
+
{/* Render it anywhere in your tree */}
|
|
25
|
+
<TopProgressBar />
|
|
26
|
+
|
|
27
|
+
<Outlet />
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Customization
|
|
35
|
+
|
|
36
|
+
You can customize the appearance using props:
|
|
37
|
+
|
|
38
|
+
### Gradient (Default)
|
|
39
|
+
By default, the progress bar uses a green gradient `["#008060", "#00c28a"]`. You can pass your own colors:
|
|
40
|
+
```tsx
|
|
41
|
+
<TopProgressBar gradient={["#ff0000", "#ff7f00"]} />
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Solid Color
|
|
45
|
+
If you prefer a solid color, use the `color` prop. This will override the gradient.
|
|
46
|
+
```tsx
|
|
47
|
+
<TopProgressBar color="#005bd3" />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Advanced Styling
|
|
51
|
+
You can also override the `boxShadow` and `height`:
|
|
52
|
+
```tsx
|
|
53
|
+
<TopProgressBar
|
|
54
|
+
color="#333"
|
|
55
|
+
height="4px"
|
|
56
|
+
boxShadow="0 2px 4px rgba(0,0,0,0.2)"
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Props API
|
|
61
|
+
|
|
62
|
+
| Prop | Type | Default | Description |
|
|
63
|
+
|------|------|---------|-------------|
|
|
64
|
+
| `gradient` | `[string, string]` | `["#008060", "#00c28a"]` | A tuple of two color strings used to generate a linear gradient. |
|
|
65
|
+
| `color` | `string` | `undefined` | A solid background color. If provided, overrides `gradient`. |
|
|
66
|
+
| `height` | `string` | `"3px"` | The height of the progress bar. |
|
|
67
|
+
| `boxShadow` | `string` | `"0 1px 8px rgba(0, 128, 96, 0.5)"` | CSS `box-shadow` string. If `color` is provided and `boxShadow` is not, it defaults to `"none"`. |
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface TopProgressBarProps {
|
|
4
|
+
/**
|
|
5
|
+
* Use a single solid color.
|
|
6
|
+
*/
|
|
7
|
+
color?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Use a gradient with two colors. Overrides `color` if provided.
|
|
10
|
+
* Default: ["#008060", "#00c28a"]
|
|
11
|
+
*/
|
|
12
|
+
gradient?: [string, string];
|
|
13
|
+
/**
|
|
14
|
+
* Custom CSS boxShadow.
|
|
15
|
+
* Default: "0 1px 8px rgba(0, 128, 96, 0.5)"
|
|
16
|
+
*/
|
|
17
|
+
boxShadow?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Custom height.
|
|
20
|
+
* Default: "3px"
|
|
21
|
+
*/
|
|
22
|
+
height?: string;
|
|
23
|
+
}
|
|
24
|
+
declare function TopProgressBar({ color, gradient, boxShadow, height, }: TopProgressBarProps): react.JSX.Element;
|
|
25
|
+
|
|
26
|
+
export { TopProgressBar, type TopProgressBarProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
interface TopProgressBarProps {
|
|
4
|
+
/**
|
|
5
|
+
* Use a single solid color.
|
|
6
|
+
*/
|
|
7
|
+
color?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Use a gradient with two colors. Overrides `color` if provided.
|
|
10
|
+
* Default: ["#008060", "#00c28a"]
|
|
11
|
+
*/
|
|
12
|
+
gradient?: [string, string];
|
|
13
|
+
/**
|
|
14
|
+
* Custom CSS boxShadow.
|
|
15
|
+
* Default: "0 1px 8px rgba(0, 128, 96, 0.5)"
|
|
16
|
+
*/
|
|
17
|
+
boxShadow?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Custom height.
|
|
20
|
+
* Default: "3px"
|
|
21
|
+
*/
|
|
22
|
+
height?: string;
|
|
23
|
+
}
|
|
24
|
+
declare function TopProgressBar({ color, gradient, boxShadow, height, }: TopProgressBarProps): react.JSX.Element;
|
|
25
|
+
|
|
26
|
+
export { TopProgressBar, type TopProgressBarProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.tsx
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
TopProgressBar: () => TopProgressBar
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var import_react_router = require("react-router");
|
|
27
|
+
var import_react = require("react");
|
|
28
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
29
|
+
function TopProgressBar({
|
|
30
|
+
color,
|
|
31
|
+
gradient,
|
|
32
|
+
boxShadow,
|
|
33
|
+
height = "3px"
|
|
34
|
+
}) {
|
|
35
|
+
const navigation = (0, import_react_router.useNavigation)();
|
|
36
|
+
const fetchers = (0, import_react_router.useFetchers)();
|
|
37
|
+
const isNavigating = navigation.state !== "idle" || fetchers.some((f) => f.state !== "idle");
|
|
38
|
+
const [progress, setProgress] = (0, import_react.useState)(0);
|
|
39
|
+
const [visible, setVisible] = (0, import_react.useState)(false);
|
|
40
|
+
const tickRef = (0, import_react.useRef)(null);
|
|
41
|
+
const fadeRef = (0, import_react.useRef)(null);
|
|
42
|
+
(0, import_react.useEffect)(() => {
|
|
43
|
+
if (isNavigating) {
|
|
44
|
+
setProgress(10);
|
|
45
|
+
setVisible(true);
|
|
46
|
+
tickRef.current = setInterval(() => {
|
|
47
|
+
setProgress((prev) => {
|
|
48
|
+
if (prev >= 90) return prev;
|
|
49
|
+
const remaining = 90 - prev;
|
|
50
|
+
const step = Math.max(1, remaining * 0.08);
|
|
51
|
+
return Math.min(90, prev + step);
|
|
52
|
+
});
|
|
53
|
+
}, 120);
|
|
54
|
+
} else {
|
|
55
|
+
if (tickRef.current) clearInterval(tickRef.current);
|
|
56
|
+
setProgress(100);
|
|
57
|
+
fadeRef.current = setTimeout(() => {
|
|
58
|
+
setVisible(false);
|
|
59
|
+
setProgress(0);
|
|
60
|
+
}, 350);
|
|
61
|
+
}
|
|
62
|
+
return () => {
|
|
63
|
+
if (tickRef.current) clearInterval(tickRef.current);
|
|
64
|
+
if (fadeRef.current) clearTimeout(fadeRef.current);
|
|
65
|
+
};
|
|
66
|
+
}, [isNavigating]);
|
|
67
|
+
const activeGradient = !color && !gradient ? ["#008060", "#00c28a"] : gradient;
|
|
68
|
+
const activeBoxShadow = boxShadow !== void 0 ? boxShadow : color ? "none" : "0 1px 8px rgba(0, 128, 96, 0.5)";
|
|
69
|
+
let backgroundStr = "transparent";
|
|
70
|
+
if (activeGradient && activeGradient.length === 2) {
|
|
71
|
+
backgroundStr = `linear-gradient(90deg, ${activeGradient[0]}, ${activeGradient[1]})`;
|
|
72
|
+
} else if (color) {
|
|
73
|
+
backgroundStr = color;
|
|
74
|
+
}
|
|
75
|
+
const barStyles = {
|
|
76
|
+
position: "fixed",
|
|
77
|
+
top: 0,
|
|
78
|
+
left: 0,
|
|
79
|
+
height,
|
|
80
|
+
zIndex: 9999,
|
|
81
|
+
background: backgroundStr,
|
|
82
|
+
boxShadow: activeBoxShadow,
|
|
83
|
+
transition: "width 200ms ease-out, opacity 300ms ease-out",
|
|
84
|
+
pointerEvents: "none"
|
|
85
|
+
};
|
|
86
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
87
|
+
"div",
|
|
88
|
+
{
|
|
89
|
+
"aria-hidden": "true",
|
|
90
|
+
role: "progressbar",
|
|
91
|
+
"aria-valuenow": Math.round(progress),
|
|
92
|
+
style: {
|
|
93
|
+
...barStyles,
|
|
94
|
+
width: `${progress}%`,
|
|
95
|
+
opacity: visible ? 1 : 0
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
101
|
+
0 && (module.exports = {
|
|
102
|
+
TopProgressBar
|
|
103
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/index.tsx
|
|
2
|
+
import { useNavigation, useFetchers } from "react-router";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
function TopProgressBar({
|
|
6
|
+
color,
|
|
7
|
+
gradient,
|
|
8
|
+
boxShadow,
|
|
9
|
+
height = "3px"
|
|
10
|
+
}) {
|
|
11
|
+
const navigation = useNavigation();
|
|
12
|
+
const fetchers = useFetchers();
|
|
13
|
+
const isNavigating = navigation.state !== "idle" || fetchers.some((f) => f.state !== "idle");
|
|
14
|
+
const [progress, setProgress] = useState(0);
|
|
15
|
+
const [visible, setVisible] = useState(false);
|
|
16
|
+
const tickRef = useRef(null);
|
|
17
|
+
const fadeRef = useRef(null);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (isNavigating) {
|
|
20
|
+
setProgress(10);
|
|
21
|
+
setVisible(true);
|
|
22
|
+
tickRef.current = setInterval(() => {
|
|
23
|
+
setProgress((prev) => {
|
|
24
|
+
if (prev >= 90) return prev;
|
|
25
|
+
const remaining = 90 - prev;
|
|
26
|
+
const step = Math.max(1, remaining * 0.08);
|
|
27
|
+
return Math.min(90, prev + step);
|
|
28
|
+
});
|
|
29
|
+
}, 120);
|
|
30
|
+
} else {
|
|
31
|
+
if (tickRef.current) clearInterval(tickRef.current);
|
|
32
|
+
setProgress(100);
|
|
33
|
+
fadeRef.current = setTimeout(() => {
|
|
34
|
+
setVisible(false);
|
|
35
|
+
setProgress(0);
|
|
36
|
+
}, 350);
|
|
37
|
+
}
|
|
38
|
+
return () => {
|
|
39
|
+
if (tickRef.current) clearInterval(tickRef.current);
|
|
40
|
+
if (fadeRef.current) clearTimeout(fadeRef.current);
|
|
41
|
+
};
|
|
42
|
+
}, [isNavigating]);
|
|
43
|
+
const activeGradient = !color && !gradient ? ["#008060", "#00c28a"] : gradient;
|
|
44
|
+
const activeBoxShadow = boxShadow !== void 0 ? boxShadow : color ? "none" : "0 1px 8px rgba(0, 128, 96, 0.5)";
|
|
45
|
+
let backgroundStr = "transparent";
|
|
46
|
+
if (activeGradient && activeGradient.length === 2) {
|
|
47
|
+
backgroundStr = `linear-gradient(90deg, ${activeGradient[0]}, ${activeGradient[1]})`;
|
|
48
|
+
} else if (color) {
|
|
49
|
+
backgroundStr = color;
|
|
50
|
+
}
|
|
51
|
+
const barStyles = {
|
|
52
|
+
position: "fixed",
|
|
53
|
+
top: 0,
|
|
54
|
+
left: 0,
|
|
55
|
+
height,
|
|
56
|
+
zIndex: 9999,
|
|
57
|
+
background: backgroundStr,
|
|
58
|
+
boxShadow: activeBoxShadow,
|
|
59
|
+
transition: "width 200ms ease-out, opacity 300ms ease-out",
|
|
60
|
+
pointerEvents: "none"
|
|
61
|
+
};
|
|
62
|
+
return /* @__PURE__ */ jsx(
|
|
63
|
+
"div",
|
|
64
|
+
{
|
|
65
|
+
"aria-hidden": "true",
|
|
66
|
+
role: "progressbar",
|
|
67
|
+
"aria-valuenow": Math.round(progress),
|
|
68
|
+
style: {
|
|
69
|
+
...barStyles,
|
|
70
|
+
width: `${progress}%`,
|
|
71
|
+
opacity: visible ? 1 : 0
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
TopProgressBar
|
|
78
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "q-top-progress-bar",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A slim progress bar for React Router navigations",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
24
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
25
|
+
"react-router": "^7.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/react": "^18.2.0",
|
|
29
|
+
"@types/react-dom": "^18.2.0",
|
|
30
|
+
"react": "^18.2.0",
|
|
31
|
+
"react-dom": "^18.2.0",
|
|
32
|
+
"react-router": "^7.0.0",
|
|
33
|
+
"tsup": "^8.0.2",
|
|
34
|
+
"typescript": "^5.4.5"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"react",
|
|
38
|
+
"react-router",
|
|
39
|
+
"progress-bar",
|
|
40
|
+
"navigation"
|
|
41
|
+
],
|
|
42
|
+
"author": "",
|
|
43
|
+
"license": "MIT"
|
|
44
|
+
}
|