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 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"`. |
@@ -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 };
@@ -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
+ }