├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .mailmap ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── MIGRATION.md ├── README.md ├── codecov.yml ├── eslint.config.mjs ├── examples ├── hoc │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── Bar.tsx │ │ ├── Container.tsx │ │ ├── Progress.tsx │ │ ├── Spinner.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── material-ui │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── Progress.tsx │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── multiple-instances │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── Bar.tsx │ │ ├── Container.tsx │ │ ├── Progress.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── next-pages-router │ ├── .gitignore │ ├── README.md │ ├── components │ │ └── Loading.tsx │ ├── next-env.d.ts │ ├── package.json │ ├── pages │ │ ├── _app.tsx │ │ ├── about.tsx │ │ ├── forever.tsx │ │ └── index.tsx │ └── tsconfig.json ├── original-design │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── Bar.tsx │ │ ├── Container.tsx │ │ ├── Progress.tsx │ │ ├── Spinner.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── plain-js │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ └── src │ │ ├── Bar.js │ │ ├── Container.js │ │ ├── Progress.js │ │ ├── Spinner.js │ │ ├── index.css │ │ └── index.js ├── reach-router │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── index.css │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── react-router-v5 │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── Bar.tsx │ │ ├── Container.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── react-router-v6 │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── Bar.tsx │ │ ├── Container.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── render-props │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── Bar.tsx │ │ ├── Container.tsx │ │ ├── Progress.tsx │ │ ├── Spinner.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ └── react-app-env.d.ts │ └── tsconfig.json ├── umd-dev │ ├── index.html │ ├── main.css │ └── package.json └── umd-prod │ ├── index.html │ ├── main.css │ └── package.json ├── index.js ├── package-lock.json ├── package.json ├── renovate.json ├── rollup.config.mjs ├── scripts └── jest │ ├── config.cjs.js │ ├── config.cjsprod.js │ ├── config.es.js │ ├── config.src.js │ ├── config.umd.js │ └── config.umdprod.js ├── src ├── NProgress.tsx ├── clamp.ts ├── createQueue.ts ├── createTimeout.ts ├── env.d.ts ├── increment.ts ├── index.tsx ├── types.ts ├── useEffectOnce.ts ├── useGetSetState.ts ├── useNProgress.tsx ├── useUpdateEffect.ts └── withNProgress.tsx ├── test ├── NProgress.spec.tsx ├── clamp.spec.ts ├── increment.spec.ts ├── queue.spec.ts ├── timeout.spec.ts ├── useEffectOnce.spec.ts ├── useGetSetState.spec.ts ├── useNProgress.spec.ts ├── useUpdateEffect.spec.ts └── withNProgress.spec.tsx ├── tsconfig.base.json ├── tsconfig.eslint.json └── tsconfig.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | ci: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: styfle/cancel-workflow-action@0.12.1 14 | with: 15 | access_token: ${{ github.token }} 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version-file: '.nvmrc' 20 | check-latest: true 21 | - run: npm ci 22 | - run: npm test 23 | - uses: codecov/codecov-action@v5 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | .vscode 4 | compiled 5 | coverage 6 | dist 7 | examples/*/package-lock.json 8 | examples/umd-dev/.cache 9 | examples/umd-prod/.cache 10 | node_modules -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Tane Morgan <464864+tanem@users.noreply.github.com> 2 | Tane Morgan <464864+tanem@users.noreply.github.com> tanem -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md 2 | compiled 3 | coverage 4 | dist 5 | package.json -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Austin <62747268+upsurge0@users.noreply.github.com> 2 | Austin Jerry 3 | Junho Yeo 4 | Renovate Bot 5 | Tane Morgan <464864+tanem@users.noreply.github.com> 6 | dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 7 | renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tane Morgan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migrating 2 | 3 | ## v5.0.0 4 | 5 | The prop-types package is no longer required for using the UMD builds. 6 | 7 | ## v4.0.0 8 | 9 | Allows multiple instances of `react-nprogress` on a page. Technically this isn't a breaking change, but it was decided to bump the major version in order to reduce the chance of bugs slipping into consuming code. 10 | 11 | ## v3.0.0 12 | 13 | The source code was refactored to use [hooks](https://reactjs.org/docs/hooks-intro.html). A `useNProgress` hook was also exposed. As a result, the `react` and `react-dom` peer dependency requirements are now `^16.8.0`. 14 | 15 | ## v2.0.0 16 | 17 | The build process was refactored in this version. Technically this isn't a breaking change, but it was decided to bump the major version in order to reduce the chance of bugs slipping into consuming code. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-nprogress 2 | 3 | [![npm version](https://img.shields.io/npm/v/@tanem/react-nprogress.svg?style=flat-square)](https://www.npmjs.com/package/@tanem/react-nprogress) 4 | [![build status](https://img.shields.io/github/actions/workflow/status/tanem/react-nprogress/ci.yml?style=flat-square)](https://github.com/tanem/react-nprogress/actions?query=workflow%3ACI) 5 | [![coverage status](https://img.shields.io/codecov/c/github/tanem/react-nprogress.svg?style=flat-square)](https://codecov.io/gh/tanem/react-nprogress) 6 | [![npm downloads](https://img.shields.io/npm/dm/@tanem/react-nprogress.svg?style=flat-square)](https://www.npmjs.com/package/@tanem/react-nprogress) 7 | [![minzipped size](https://img.shields.io/bundlephobia/minzip/@tanem/react-nprogress?style=flat-square)](https://bundlephobia.com/result?p=@tanem/react-nprogress) 8 | 9 | > A React primitive for building slim progress bars. 10 | 11 | [Background](#background) | [Usage](#usage) | [Live Examples](#live-examples) | [API](#api) | [Installation](#installation) | [License](#license) 12 | 13 | ## Background 14 | 15 | This is a React port of [rstacruz](https://github.com/rstacruz)'s [`nprogress`](https://github.com/rstacruz/nprogress) module. It exposes an API that encapsulates the logic of `nprogress` and renders nothing, giving you complete control over rendering. 16 | 17 | ## Usage 18 | 19 | In the following examples, `Container`, `Bar` and `Spinner` are custom components. 20 | 21 | **Hook** 22 | 23 | ```jsx 24 | import { useNProgress } from '@tanem/react-nprogress' 25 | import React from 'react' 26 | import { render } from 'react-dom' 27 | 28 | import Bar from './Bar' 29 | import Container from './Container' 30 | import Spinner from './Spinner' 31 | 32 | const Progress = ({ isAnimating }) => { 33 | const { animationDuration, isFinished, progress } = useNProgress({ 34 | isAnimating, 35 | }) 36 | 37 | return ( 38 | 39 | 40 | 41 | 42 | ) 43 | } 44 | 45 | render(, document.getElementById('root')) 46 | ``` 47 | 48 | **Render Props** 49 | 50 | ```jsx 51 | import { NProgress } from '@tanem/react-nprogress' 52 | import React from 'react' 53 | import { render } from 'react-dom' 54 | 55 | import Bar from './Bar' 56 | import Container from './Container' 57 | import Spinner from './Spinner' 58 | 59 | render( 60 | 61 | {({ animationDuration, isFinished, progress }) => ( 62 | 63 | 64 | 65 | 66 | )} 67 | , 68 | document.getElementById('root') 69 | ) 70 | ``` 71 | 72 | **HOC** 73 | 74 | ```jsx 75 | import { withNProgress } from '@tanem/react-nprogress' 76 | import React from 'react' 77 | import { render } from 'react-dom' 78 | 79 | import Bar from './Bar' 80 | import Container from './Container' 81 | import Spinner from './Spinner' 82 | 83 | const Inner = ({ animationDuration, isFinished, progress }) => ( 84 | 85 | 86 | 87 | 88 | ) 89 | 90 | const Enhanced = withNProgress(Inner) 91 | 92 | render(, document.getElementById('root')) 93 | ``` 94 | 95 | ## Live Examples 96 | 97 | - HOC: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/hoc) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/hoc) 98 | - Material UI: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/material-ui) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/material-ui) 99 | - Multiple Instances: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/multiple-instances) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/multiple-instances) 100 | - Next Pages Router: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/next-pages-router) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/next-pages-router) 101 | - Original Design: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/original-design) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/original-design) 102 | - Plain JS: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/plain-js) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/plain-js) 103 | - Reach Router: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/reach-router) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/reach-router) 104 | - React Router V5: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/react-router-v5) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/react-router-v5) 105 | - React Router V6: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/react-router-v6) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/react-router-v6) 106 | - Render Props: [Source](https://github.com/tanem/react-nprogress/tree/master/examples/render-props) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/render-props) 107 | - UMD Build (Development): [Source](https://github.com/tanem/react-nprogress/tree/master/examples/umd-dev) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/umd-dev) 108 | - UMD Build (Production): [Source](https://github.com/tanem/react-nprogress/tree/master/examples/umd-prod) | [Sandbox](https://codesandbox.io/s/github/tanem/react-nprogress/tree/master/examples/umd-prod) 109 | 110 | ## API 111 | 112 | **Props** 113 | 114 | - `animationDuration` - _Optional_ Number indicating the animation duration in `ms`. Defaults to `200`. 115 | - `incrementDuration` - _Optional_ Number indicating the length of time between progress bar increments in `ms`. Defaults to `800`. 116 | - `isAnimating` - _Optional_ Boolean indicating if the progress bar is animating. Defaults to `false`. 117 | - `minimum` - _Optional_ Number between `0` and `1` indicating the minimum value of the progress bar. Defaults to `0.08`. 118 | 119 | **Hook Example** 120 | 121 | ```jsx 122 | const Progress = ({ 123 | animationDuration, 124 | incrementDuration, 125 | isAnimating, 126 | minimum 127 | }) => { 128 | const { isFinished, progress } = useNProgress({ 129 | animationDuration, 130 | incrementDuration, 131 | isAnimating, 132 | minimum 133 | }) 134 | 135 | return ( 136 | 137 | 138 | 139 | 140 | ) 141 | } 142 | 143 | 149 | ``` 150 | 151 | **Render Props Example** 152 | 153 | ```jsx 154 | 160 | {({ animationDuration, isFinished, progress }) => ( 161 | 162 | 163 | 164 | 165 | )} 166 | 167 | ``` 168 | 169 | **HOC Example** 170 | 171 | ```jsx 172 | const Inner = ({ animationDuration, isFinished, progress }) => ( 173 | 174 | 175 | 176 | 177 | ) 178 | 179 | const Enhanced = withNProgress(Inner) 180 | 181 | 187 | ``` 188 | 189 | ## Installation 190 | 191 | ``` 192 | $ npm install @tanem/react-nprogress 193 | ``` 194 | 195 | UMD builds are also available for use with pre-React 19 via [unpkg](https://unpkg.com/): 196 | 197 | - https://unpkg.com/@tanem/react-nprogress/dist/react-nprogress.umd.development.js 198 | - https://unpkg.com/@tanem/react-nprogress/dist/react-nprogress.umd.production.js 199 | 200 | For the non-minified development version, make sure you have already included: 201 | 202 | - [`React`](https://unpkg.com/react@18/umd/react.development.js) 203 | - [`ReactDOM`](https://unpkg.com/react-dom@18/umd/react-dom.development.js) 204 | 205 | For the minified production version, make sure you have already included: 206 | 207 | - [`React`](https://unpkg.com/react@18/umd/react.production.min.js) 208 | - [`ReactDOM`](https://unpkg.com/react-dom@18/umd/react-dom.production.min.js) 209 | 210 | ## License 211 | 212 | MIT 213 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | coverage: 3 | status: 4 | patch: off 5 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { fileURLToPath } from 'node:url' 3 | 4 | import { fixupPluginRules } from '@eslint/compat' 5 | import { FlatCompat } from '@eslint/eslintrc' 6 | import js from '@eslint/js' 7 | import typescriptEslint from '@typescript-eslint/eslint-plugin' 8 | import tsParser from '@typescript-eslint/parser' 9 | import react from 'eslint-plugin-react' 10 | import reactHooks from 'eslint-plugin-react-hooks' 11 | import simpleImportSort from 'eslint-plugin-simple-import-sort' 12 | 13 | const __filename = fileURLToPath(import.meta.url) 14 | const __dirname = path.dirname(__filename) 15 | const compat = new FlatCompat({ 16 | allConfig: js.configs.all, 17 | baseDirectory: __dirname, 18 | recommendedConfig: js.configs.recommended, 19 | }) 20 | 21 | export default [ 22 | { 23 | ignores: ['**/compiled/', '**/coverage/', '**/dist/', '**/node_modules/'], 24 | }, 25 | ...compat.extends( 26 | 'plugin:react/recommended', 27 | 'plugin:@typescript-eslint/recommended', 28 | 'prettier', 29 | ), 30 | { 31 | languageOptions: { 32 | ecmaVersion: 5, 33 | parser: tsParser, 34 | parserOptions: { 35 | ecmaFeatures: { 36 | jsx: true, 37 | }, 38 | project: path.join(__dirname, 'tsconfig.eslint.json'), 39 | }, 40 | sourceType: 'module', 41 | }, 42 | 43 | plugins: { 44 | '@typescript-eslint': typescriptEslint, 45 | react, 46 | 'react-hooks': fixupPluginRules(reactHooks), 47 | 'simple-import-sort': simpleImportSort, 48 | }, 49 | 50 | rules: { 51 | 'react-hooks/exhaustive-deps': 'warn', 52 | 'react-hooks/rules-of-hooks': 'error', 53 | 'react/jsx-sort-props': 'error', 54 | 'react/jsx-uses-react': 'off', 55 | 'react/react-in-jsx-scope': 'off', 56 | 'simple-import-sort/exports': 'error', 57 | 'simple-import-sort/imports': 'error', 58 | 'sort-imports': 'off', 59 | 'sort-keys': 'error', 60 | }, 61 | 62 | settings: { 63 | react: { 64 | version: 'detect', 65 | }, 66 | }, 67 | }, 68 | { 69 | files: ['**/*.js'], 70 | rules: { 71 | '@typescript-eslint/explicit-module-boundary-types': 'off', 72 | '@typescript-eslint/no-require-imports': 'off', 73 | }, 74 | }, 75 | { 76 | files: ['examples/**/*'], 77 | rules: { 78 | '@typescript-eslint/explicit-module-boundary-types': 'off', 79 | 'react/no-unknown-property': [ 80 | 'error', 81 | { 82 | ignore: ['jsx'], 83 | }, 84 | ], 85 | 'react/prop-types': 'off', 86 | }, 87 | }, 88 | ] 89 | -------------------------------------------------------------------------------- /examples/hoc/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_DESCRIPTION=$npm_package_description 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/hoc/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /examples/hoc/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress HOC Example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
15 | You will also see any lint errors in the console. 16 | -------------------------------------------------------------------------------- /examples/hoc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hoc", 3 | "description": "ReactNProgress HOC Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "dependencies": { 10 | "@tanem/react-nprogress": "latest", 11 | "@types/jest": "30.0.0", 12 | "@types/node": "22.15.32", 13 | "@types/react": "19.1.8", 14 | "@types/react-dom": "19.1.6", 15 | "react": "19.1.0", 16 | "react-dom": "19.1.0", 17 | "react-scripts": "5.0.1", 18 | "typescript": "4.9.5" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /examples/hoc/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %REACT_APP_DESCRIPTION% 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/hoc/src/Bar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Bar: React.FC<{ animationDuration: number; progress: number }> = ({ 4 | animationDuration, 5 | progress, 6 | }) => ( 7 |
20 |
32 |
33 | ) 34 | 35 | export default Bar 36 | -------------------------------------------------------------------------------- /examples/hoc/src/Container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Container: React.FC<{ 4 | animationDuration: number 5 | isFinished: boolean 6 | }> = ({ animationDuration, children, isFinished }) => ( 7 |
14 | {children} 15 |
16 | ) 17 | 18 | export default Container 19 | -------------------------------------------------------------------------------- /examples/hoc/src/Progress.tsx: -------------------------------------------------------------------------------- 1 | import { withNProgress } from '@tanem/react-nprogress' 2 | import React from 'react' 3 | 4 | import Bar from './Bar' 5 | import Container from './Container' 6 | import Spinner from './Spinner' 7 | 8 | const Progress: React.FC<{ 9 | animationDuration: number 10 | isFinished: boolean 11 | progress: number 12 | }> = ({ isFinished, progress, animationDuration }) => ( 13 | 14 | 15 | 16 | 17 | ) 18 | 19 | export default withNProgress(Progress) 20 | -------------------------------------------------------------------------------- /examples/hoc/src/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Spinner: React.FC = () => ( 4 |
13 |
26 |
27 | ) 28 | 29 | export default Spinner 30 | -------------------------------------------------------------------------------- /examples/hoc/src/index.css: -------------------------------------------------------------------------------- 1 | @keyframes spinner { 2 | 0% { 3 | transform: rotate(0deg); 4 | } 5 | 100% { 6 | transform: rotate(360deg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/hoc/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React, { useState } from 'react' 4 | import { createRoot } from 'react-dom/client' 5 | 6 | import Progress from './Progress' 7 | 8 | const App: React.FC = () => { 9 | const [state, setState] = useState({ 10 | isAnimating: false, 11 | key: 0, 12 | }) 13 | 14 | return ( 15 | <> 16 | 17 | 27 | 28 | ) 29 | } 30 | 31 | const container = document.getElementById('root') 32 | const root = createRoot(container!) 33 | root.render() 34 | -------------------------------------------------------------------------------- /examples/hoc/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/hoc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/material-ui/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_DESCRIPTION=$npm_package_description 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/material-ui/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /examples/material-ui/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress Material UI Example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
15 | You will also see any lint errors in the console. -------------------------------------------------------------------------------- /examples/material-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "material-ui", 3 | "description": "ReactNProgress Material UI Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "dependencies": { 10 | "@emotion/react": "11.14.0", 11 | "@emotion/styled": "11.14.0", 12 | "@mui/material": "7.1.2", 13 | "@mui/styles": "6.4.8", 14 | "@tanem/react-nprogress": "latest", 15 | "@types/jest": "30.0.0", 16 | "@types/node": "22.15.32", 17 | "@types/react": "19.1.8", 18 | "@types/react-dom": "19.1.6", 19 | "react": "19.1.0", 20 | "react-dom": "19.1.0", 21 | "react-scripts": "5.0.1", 22 | "typescript": "4.9.5" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start" 26 | }, 27 | "browserslist": [ 28 | ">0.2%", 29 | "not dead", 30 | "not ie <= 11", 31 | "not op_mini all" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/material-ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %REACT_APP_DESCRIPTION% 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/material-ui/src/Progress.tsx: -------------------------------------------------------------------------------- 1 | import Container from '@mui/material/Container' 2 | import LinearProgress from '@mui/material/LinearProgress' 3 | import { makeStyles, Theme } from '@mui/styles' 4 | import { useNProgress } from '@tanem/react-nprogress' 5 | import React from 'react' 6 | 7 | const useStyles = makeStyles< 8 | Theme, 9 | Pick, 'animationDuration' | 'isFinished'> 10 | >({ 11 | bar: ({ animationDuration }) => ({ 12 | transitionDuration: `${animationDuration}ms`, 13 | }), 14 | container: ({ animationDuration, isFinished }) => ({ 15 | opacity: isFinished ? 0 : 1, 16 | pointerEvents: 'none', 17 | transition: `opacity ${animationDuration}ms linear`, 18 | }), 19 | }) 20 | 21 | const Progress: React.FC<{ isAnimating: boolean }> = ({ isAnimating }) => { 22 | const { animationDuration, isFinished, progress } = useNProgress({ 23 | isAnimating, 24 | }) 25 | const classes = useStyles({ animationDuration, isFinished }) 26 | 27 | return ( 28 | 29 | 34 | 35 | ) 36 | } 37 | 38 | export default Progress 39 | -------------------------------------------------------------------------------- /examples/material-ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@mui/material/Button' 2 | import Container from '@mui/material/Container' 3 | import { makeStyles } from '@mui/styles' 4 | import React, { useState } from 'react' 5 | import { createRoot } from 'react-dom/client' 6 | 7 | import Progress from './Progress' 8 | 9 | const useStyles = makeStyles({ 10 | '@global': { 11 | body: { 12 | margin: 0, 13 | }, 14 | }, 15 | container: { 16 | alignItems: 'center', 17 | display: 'flex', 18 | height: '100vh', 19 | justifyContent: 'center', 20 | }, 21 | }) 22 | 23 | const App: React.FC = () => { 24 | const classes = useStyles() 25 | const [state, setState] = useState({ 26 | isAnimating: false, 27 | key: 0, 28 | }) 29 | 30 | return ( 31 | <> 32 | 33 | 34 | 45 | 46 | 47 | ) 48 | } 49 | 50 | const container = document.getElementById('root') 51 | const root = createRoot(container!) 52 | root.render() 53 | -------------------------------------------------------------------------------- /examples/material-ui/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/material-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/multiple-instances/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_DESCRIPTION=$npm_package_description 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/multiple-instances/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /examples/multiple-instances/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress Multiple Instances Example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
15 | You will also see any lint errors in the console. -------------------------------------------------------------------------------- /examples/multiple-instances/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multiple-instances", 3 | "description": "ReactNProgress Multiple Instances Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "dependencies": { 10 | "@tanem/react-nprogress": "latest", 11 | "@types/jest": "30.0.0", 12 | "@types/node": "22.15.32", 13 | "@types/react": "19.1.8", 14 | "@types/react-dom": "19.1.6", 15 | "react": "19.1.0", 16 | "react-dom": "19.1.0", 17 | "react-scripts": "5.0.1", 18 | "typescript": "4.9.5" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /examples/multiple-instances/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %REACT_APP_DESCRIPTION% 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/multiple-instances/src/Bar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Bar: React.FC<{ 4 | animationDuration: number 5 | progress: number 6 | }> = ({ animationDuration, progress }) => ( 7 |
20 | ) 21 | 22 | export default Bar 23 | -------------------------------------------------------------------------------- /examples/multiple-instances/src/Container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Container: React.FC<{ 4 | animationDuration: number 5 | isFinished: boolean 6 | }> = ({ animationDuration, children, isFinished }) => ( 7 |
14 | {children} 15 |
16 | ) 17 | 18 | export default Container 19 | -------------------------------------------------------------------------------- /examples/multiple-instances/src/Progress.tsx: -------------------------------------------------------------------------------- 1 | import { useNProgress } from '@tanem/react-nprogress' 2 | import React from 'react' 3 | 4 | import Bar from './Bar' 5 | import Container from './Container' 6 | 7 | const Progress: React.FC<{ 8 | isAnimating: boolean 9 | position: 'top' | 'bottom' 10 | }> = ({ isAnimating }) => { 11 | const { animationDuration, isFinished, progress } = useNProgress({ 12 | isAnimating, 13 | }) 14 | 15 | return ( 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default Progress 23 | -------------------------------------------------------------------------------- /examples/multiple-instances/src/index.css: -------------------------------------------------------------------------------- 1 | @keyframes spinner { 2 | 0% { 3 | transform: rotate(0deg); 4 | } 5 | 100% { 6 | transform: rotate(360deg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/multiple-instances/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React, { useState } from 'react' 4 | import { createRoot } from 'react-dom/client' 5 | 6 | import Progress from './Progress' 7 | 8 | const A: React.FC = () => { 9 | const [state, setState] = useState({ 10 | isAnimating: false, 11 | key: 0, 12 | }) 13 | 14 | return ( 15 |
16 | 21 | 31 |
32 | ) 33 | } 34 | 35 | const B: React.FC = () => { 36 | const [state, setState] = useState({ 37 | isAnimating: false, 38 | key: 0, 39 | }) 40 | 41 | return ( 42 |
43 | 48 | 58 |
59 | ) 60 | } 61 | 62 | const App: React.FC = () => ( 63 | <> 64 | 65 | 66 | 67 | ) 68 | 69 | const container = document.getElementById('root') 70 | const root = createRoot(container!) 71 | root.render() 72 | -------------------------------------------------------------------------------- /examples/multiple-instances/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/multiple-instances/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/next-pages-router/.gitignore: -------------------------------------------------------------------------------- 1 | .next/ 2 | /node_modules -------------------------------------------------------------------------------- /examples/next-pages-router/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress Next Pages Router Example 2 | 3 | This project is based on [the Next.js `with-loading` example](https://github.com/zeit/next.js/tree/canary/examples/with-loading). To run it: 4 | 5 | ``` 6 | $ npm i && npm run dev 7 | ``` 8 | 9 | Then open [http://localhost:3000](http://localhost:3000) to view it in the browser. 10 | -------------------------------------------------------------------------------- /examples/next-pages-router/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { useNProgress } from '@tanem/react-nprogress' 2 | 3 | const Loading: React.FC<{ isRouteChanging: boolean }> = ({ 4 | isRouteChanging, 5 | }) => { 6 | const { animationDuration, isFinished, progress } = useNProgress({ 7 | isAnimating: isRouteChanging, 8 | }) 9 | 10 | return ( 11 | <> 12 | 44 |
45 |
46 |
47 |
48 |
49 | 50 | ) 51 | } 52 | 53 | export default Loading 54 | -------------------------------------------------------------------------------- /examples/next-pages-router/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/next-pages-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-pages-router", 3 | "description": "ReactNProgress Next Pages Router Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "main": "index.js", 10 | "scripts": { 11 | "dev": "next", 12 | "build": "next build", 13 | "start": "next start" 14 | }, 15 | "dependencies": { 16 | "@tanem/react-nprogress": "latest", 17 | "next": "latest", 18 | "react": "19.1.0", 19 | "react-dom": "19.1.0" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "22.15.32", 23 | "@types/react": "19.1.8", 24 | "typescript": "4.9.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/next-pages-router/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from 'next/app' 2 | import Link from 'next/link' 3 | import { useRouter } from 'next/router' 4 | import { useEffect, useState } from 'react' 5 | 6 | import Loading from '../components/Loading' 7 | 8 | const App: React.FC = ({ Component, pageProps }) => { 9 | const router = useRouter() 10 | 11 | const [state, setState] = useState({ 12 | isRouteChanging: false, 13 | loadingKey: 0, 14 | }) 15 | 16 | useEffect(() => { 17 | const handleRouteChangeStart = () => { 18 | setState((prevState) => ({ 19 | ...prevState, 20 | isRouteChanging: true, 21 | loadingKey: prevState.loadingKey ^ 1, 22 | })) 23 | } 24 | 25 | const handleRouteChangeEnd = () => { 26 | setState((prevState) => ({ 27 | ...prevState, 28 | isRouteChanging: false, 29 | })) 30 | } 31 | 32 | router.events.on('routeChangeStart', handleRouteChangeStart) 33 | router.events.on('routeChangeComplete', handleRouteChangeEnd) 34 | router.events.on('routeChangeError', handleRouteChangeEnd) 35 | 36 | return () => { 37 | router.events.off('routeChangeStart', handleRouteChangeStart) 38 | router.events.off('routeChangeComplete', handleRouteChangeEnd) 39 | router.events.off('routeChangeError', handleRouteChangeEnd) 40 | } 41 | }, [router.events]) 42 | 43 | return ( 44 | <> 45 | 46 |
56 | 57 | 58 | ) 59 | } 60 | 61 | export default App 62 | -------------------------------------------------------------------------------- /examples/next-pages-router/pages/about.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const AboutPage: React.FC = () =>

This is about Next.js!

4 | 5 | export async function getServerSideProps() { 6 | await new Promise((resolve) => { 7 | setTimeout(resolve, 500) 8 | }) 9 | return { props: {} } 10 | } 11 | 12 | export default AboutPage 13 | -------------------------------------------------------------------------------- /examples/next-pages-router/pages/forever.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const ForeverPage: React.FC = () =>

This page was rendered for a while!

4 | 5 | export async function getServerSideProps() { 6 | await new Promise((resolve) => { 7 | setTimeout(resolve, 3000) 8 | }) 9 | return { props: {} } 10 | } 11 | 12 | export default ForeverPage 13 | -------------------------------------------------------------------------------- /examples/next-pages-router/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IndexPage: React.FC = () =>

Hello Next.js!

4 | 5 | export default IndexPage 6 | -------------------------------------------------------------------------------- /examples/next-pages-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /examples/original-design/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_DESCRIPTION=$npm_package_description 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/original-design/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /examples/original-design/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress Original Design Example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
15 | You will also see any lint errors in the console. -------------------------------------------------------------------------------- /examples/original-design/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "original-design", 3 | "description": "ReactNProgress Original Design Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "dependencies": { 10 | "@tanem/react-nprogress": "latest", 11 | "@types/jest": "30.0.0", 12 | "@types/node": "22.15.32", 13 | "@types/react": "19.1.8", 14 | "@types/react-dom": "19.1.6", 15 | "react": "19.1.0", 16 | "react-dom": "19.1.0", 17 | "react-scripts": "5.0.1", 18 | "typescript": "4.9.5" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /examples/original-design/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %REACT_APP_DESCRIPTION% 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/original-design/src/Bar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Bar: React.FC<{ animationDuration: number; progress: number }> = ({ 4 | animationDuration, 5 | progress, 6 | }) => ( 7 |
20 |
32 |
33 | ) 34 | 35 | export default Bar 36 | -------------------------------------------------------------------------------- /examples/original-design/src/Container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Container: React.FC<{ 4 | animationDuration: number 5 | isFinished: boolean 6 | }> = ({ animationDuration, children, isFinished }) => ( 7 |
14 | {children} 15 |
16 | ) 17 | 18 | export default Container 19 | -------------------------------------------------------------------------------- /examples/original-design/src/Progress.tsx: -------------------------------------------------------------------------------- 1 | import { useNProgress } from '@tanem/react-nprogress' 2 | import React from 'react' 3 | 4 | import Bar from './Bar' 5 | import Container from './Container' 6 | import Spinner from './Spinner' 7 | 8 | const Progress: React.FC<{ isAnimating: boolean }> = ({ isAnimating }) => { 9 | const { animationDuration, isFinished, progress } = useNProgress({ 10 | isAnimating, 11 | }) 12 | 13 | return ( 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default Progress 22 | -------------------------------------------------------------------------------- /examples/original-design/src/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Spinner: React.FC = () => ( 4 |
13 |
26 |
27 | ) 28 | 29 | export default Spinner 30 | -------------------------------------------------------------------------------- /examples/original-design/src/index.css: -------------------------------------------------------------------------------- 1 | @keyframes spinner { 2 | 0% { 3 | transform: rotate(0deg); 4 | } 5 | 100% { 6 | transform: rotate(360deg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/original-design/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React, { useState } from 'react' 4 | import { createRoot } from 'react-dom/client' 5 | 6 | import Progress from './Progress' 7 | 8 | const App: React.FC = () => { 9 | const [state, setState] = useState({ 10 | isAnimating: false, 11 | key: 0, 12 | }) 13 | 14 | return ( 15 | <> 16 | 17 | 27 | 28 | ) 29 | } 30 | 31 | const container = document.getElementById('root') 32 | const root = createRoot(container!) 33 | root.render() 34 | -------------------------------------------------------------------------------- /examples/original-design/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/original-design/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/plain-js/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_DESCRIPTION=$npm_package_description 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/plain-js/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /examples/plain-js/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress Plain JS Example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
15 | You will also see any lint errors in the console. -------------------------------------------------------------------------------- /examples/plain-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plain-js", 3 | "description": "ReactNProgress Plain JS Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "dependencies": { 10 | "@tanem/react-nprogress": "latest", 11 | "react": "19.1.0", 12 | "react-dom": "19.1.0", 13 | "react-scripts": "5.0.1" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start" 17 | }, 18 | "browserslist": [ 19 | ">0.2%", 20 | "not dead", 21 | "not ie <= 11", 22 | "not op_mini all" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /examples/plain-js/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %REACT_APP_DESCRIPTION% 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/plain-js/src/Bar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Bar = ({ animationDuration, progress }) => ( 4 |
17 |
29 |
30 | ) 31 | 32 | export default Bar 33 | -------------------------------------------------------------------------------- /examples/plain-js/src/Container.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Container = ({ animationDuration, children, isFinished }) => ( 4 |
11 | {children} 12 |
13 | ) 14 | 15 | export default Container 16 | -------------------------------------------------------------------------------- /examples/plain-js/src/Progress.js: -------------------------------------------------------------------------------- 1 | import { useNProgress } from '@tanem/react-nprogress' 2 | import React from 'react' 3 | 4 | import Bar from './Bar' 5 | import Container from './Container' 6 | import Spinner from './Spinner' 7 | 8 | const Progress = ({ isAnimating }) => { 9 | const { animationDuration, isFinished, progress } = useNProgress({ 10 | isAnimating, 11 | }) 12 | 13 | return ( 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | export default Progress 22 | -------------------------------------------------------------------------------- /examples/plain-js/src/Spinner.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Spinner = () => ( 4 |
13 |
26 |
27 | ) 28 | 29 | export default Spinner 30 | -------------------------------------------------------------------------------- /examples/plain-js/src/index.css: -------------------------------------------------------------------------------- 1 | @keyframes spinner { 2 | 0% { 3 | transform: rotate(0deg); 4 | } 5 | 100% { 6 | transform: rotate(360deg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/plain-js/src/index.js: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React, { useState } from 'react' 4 | import { createRoot } from 'react-dom/client' 5 | 6 | import Progress from './Progress' 7 | 8 | const App = () => { 9 | const [state, setState] = useState({ 10 | isAnimating: false, 11 | key: 0, 12 | }) 13 | 14 | return ( 15 | <> 16 | 17 | 27 | 28 | ) 29 | } 30 | 31 | const container = document.getElementById('root') 32 | const root = createRoot(container) 33 | root.render() 34 | -------------------------------------------------------------------------------- /examples/reach-router/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_DESCRIPTION=$npm_package_description 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/reach-router/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /examples/reach-router/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress Reach Router Example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
15 | You will also see any lint errors in the console. -------------------------------------------------------------------------------- /examples/reach-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reach-router", 3 | "description": "ReactNProgress Reach Router Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "dependencies": { 10 | "@reach/router": "1.3.4", 11 | "@tanem/react-nprogress": "latest", 12 | "@types/jest": "30.0.0", 13 | "@types/node": "22.15.32", 14 | "@types/reach__router": "1.3.15", 15 | "@types/react": "19.1.8", 16 | "@types/react-dom": "19.1.6", 17 | "@types/react-transition-group": "4.4.12", 18 | "react": "19.1.0", 19 | "react-dom": "19.1.0", 20 | "react-scripts": "5.0.1", 21 | "react-transition-group": "4.4.5", 22 | "typescript": "4.9.5" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start" 26 | }, 27 | "browserslist": [ 28 | ">0.2%", 29 | "not dead", 30 | "not ie <= 11", 31 | "not op_mini all" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/reach-router/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %REACT_APP_DESCRIPTION% 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/reach-router/src/index.css: -------------------------------------------------------------------------------- 1 | a { 2 | color: black; 3 | padding: 10px; 4 | display: block; 5 | } 6 | 7 | .app { 8 | position: absolute; 9 | top: 0; 10 | bottom: 0; 11 | left: 0; 12 | right: 0; 13 | display: flex; 14 | flex-direction: column; 15 | } 16 | 17 | .nav { 18 | display: flex; 19 | justify-content: space-around; 20 | } 21 | 22 | .transition-group { 23 | flex: 1; 24 | position: relative; 25 | } 26 | 27 | .router { 28 | position: absolute; 29 | top: 0; 30 | left: 0; 31 | bottom: 0; 32 | right: 0; 33 | } 34 | 35 | .page { 36 | position: absolute; 37 | top: 0; 38 | left: 0; 39 | bottom: 0; 40 | right: 0; 41 | color: white; 42 | text-align: center; 43 | font-size: 100px; 44 | font-style: italic; 45 | font-family: Times; 46 | padding-top: 20px; 47 | } 48 | 49 | .fade-enter .page { 50 | opacity: 0; 51 | z-index: 1; 52 | } 53 | 54 | .fade-enter.fade-enter-active .page { 55 | opacity: 1; 56 | transition: opacity 450ms ease-in; 57 | } 58 | -------------------------------------------------------------------------------- /examples/reach-router/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import { Link, Location, RouteComponentProps, Router } from '@reach/router' 4 | import { useNProgress } from '@tanem/react-nprogress' 5 | import React, { useState } from 'react' 6 | import { createRoot } from 'react-dom/client' 7 | import { CSSTransition, TransitionGroup } from 'react-transition-group' 8 | 9 | const App: React.FC = () => ( 10 |
11 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | ) 22 | 23 | const Page: React.FC> = ({ page }) => ( 24 |
32 | {page} 33 |
34 | ) 35 | 36 | const Container: React.FC<{ 37 | animationDuration: number 38 | isFinished: boolean 39 | }> = ({ animationDuration, children, isFinished }) => ( 40 |
47 | {children} 48 |
49 | ) 50 | 51 | const Bar: React.FC<{ animationDuration: number; progress: number }> = ({ 52 | animationDuration, 53 | progress, 54 | }) => ( 55 |
68 |
80 |
81 | ) 82 | 83 | const Progress: React.FC<{ isAnimating: boolean }> = ({ isAnimating }) => { 84 | const { animationDuration, isFinished, progress } = useNProgress({ 85 | isAnimating, 86 | }) 87 | 88 | return ( 89 | 90 | 91 | 92 | ) 93 | } 94 | 95 | const FadeTransitionRouter: React.FC = ({ children }) => { 96 | const [isLoading, setIsLoading] = useState(false) 97 | const nodeRef = React.useRef(null) 98 | 99 | return ( 100 | 101 | {({ location }) => ( 102 | 103 | 104 | 105 | { 110 | setIsLoading(true) 111 | }} 112 | onEntered={() => { 113 | setIsLoading(false) 114 | }} 115 | timeout={500} 116 | > 117 | 118 | {children} 119 | 120 | 121 | 122 | 123 | )} 124 | 125 | ) 126 | } 127 | 128 | const container = document.getElementById('root') 129 | const root = createRoot(container!) 130 | root.render() 131 | -------------------------------------------------------------------------------- /examples/reach-router/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/reach-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/react-router-v5/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_DESCRIPTION=$npm_package_description 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/react-router-v5/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /examples/react-router-v5/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress React Router V5 Example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
15 | You will also see any lint errors in the console. -------------------------------------------------------------------------------- /examples/react-router-v5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-router-v5", 3 | "description": "ReactNProgress React Router V5 Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "dependencies": { 10 | "@tanem/react-nprogress": "latest", 11 | "@types/jest": "30.0.0", 12 | "@types/node": "22.15.32", 13 | "@types/react": "19.1.8", 14 | "@types/react-dom": "19.1.6", 15 | "@types/react-router-dom": "5.3.3", 16 | "@types/react-transition-group": "4.4.12", 17 | "react": "19.1.0", 18 | "react-dom": "19.1.0", 19 | "react-router-dom": "5.3.4", 20 | "react-scripts": "5.0.1", 21 | "react-transition-group": "4.4.5", 22 | "typescript": "4.9.5" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start" 26 | }, 27 | "browserslist": [ 28 | ">0.2%", 29 | "not dead", 30 | "not ie <= 11", 31 | "not op_mini all" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/react-router-v5/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %REACT_APP_DESCRIPTION% 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-router-v5/src/Bar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Bar: React.FC<{ 4 | animationDuration: number 5 | progress: number 6 | }> = ({ animationDuration, progress }) => ( 7 |
20 |
32 |
33 | ) 34 | 35 | export default Bar 36 | -------------------------------------------------------------------------------- /examples/react-router-v5/src/Container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Container: React.FC<{ 4 | animationDuration: number 5 | isFinished: boolean 6 | }> = ({ animationDuration, children, isFinished }) => ( 7 |
14 | {children} 15 |
16 | ) 17 | 18 | export default Container 19 | -------------------------------------------------------------------------------- /examples/react-router-v5/src/index.css: -------------------------------------------------------------------------------- 1 | .fade-enter { 2 | opacity: 0; 3 | z-index: 1; 4 | } 5 | 6 | .fade-enter.fade-enter-active { 7 | opacity: 1; 8 | /* 9 | Duration has been increased by 4x from the original version for demo purposes. 10 | */ 11 | transition: opacity 1000ms ease-in; 12 | } 13 | 14 | @keyframes spinner { 15 | 0% { 16 | transform: rotate(0deg); 17 | } 18 | 100% { 19 | transform: rotate(360deg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-router-v5/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import { useNProgress } from '@tanem/react-nprogress' 4 | import React, { useState } from 'react' 5 | import { createRoot } from 'react-dom/client' 6 | import { 7 | BrowserRouter as Router, 8 | Link, 9 | LinkProps, 10 | Redirect, 11 | Route, 12 | RouteComponentProps, 13 | Switch, 14 | } from 'react-router-dom' 15 | import { CSSTransition, TransitionGroup } from 'react-transition-group' 16 | 17 | import Bar from './Bar' 18 | import Container from './Container' 19 | 20 | const styles: { [key: string]: React.CSSProperties } = {} 21 | 22 | styles.fill = { 23 | bottom: 0, 24 | left: 0, 25 | position: 'absolute', 26 | right: 0, 27 | top: 0, 28 | } 29 | 30 | styles.content = { 31 | ...styles.fill, 32 | textAlign: 'center', 33 | top: '40px', 34 | } 35 | 36 | styles.nav = { 37 | display: 'flex', 38 | height: '40px', 39 | margin: 0, 40 | padding: 0, 41 | position: 'absolute', 42 | top: 0, 43 | width: '100%', 44 | } 45 | 46 | styles.navItem = { 47 | flex: 1, 48 | listStyleType: 'none', 49 | padding: '10px', 50 | textAlign: 'center', 51 | } 52 | 53 | styles.hsl = { 54 | ...styles.fill, 55 | color: 'white', 56 | fontSize: '30px', 57 | paddingTop: '20px', 58 | } 59 | 60 | styles.rgb = { 61 | ...styles.fill, 62 | color: 'white', 63 | fontSize: '30px', 64 | paddingTop: '20px', 65 | } 66 | 67 | const NavLink: React.FC = (props) => ( 68 |
  • 69 | 70 |
  • 71 | ) 72 | 73 | const HSL: React.FC< 74 | RouteComponentProps<{ 75 | h: string 76 | s: string 77 | l: string 78 | }> 79 | > = ({ match: { params } }) => ( 80 |
    87 | hsl( 88 | {params.h}, {params.s} 89 | %, {params.l} 90 | %) 91 |
    92 | ) 93 | 94 | const RGB: React.FC< 95 | RouteComponentProps<{ 96 | r: string 97 | g: string 98 | b: string 99 | }> 100 | > = ({ match: { params } }) => ( 101 |
    108 | rgb( 109 | {params.r}, {params.g}, {params.b}) 110 |
    111 | ) 112 | 113 | const Progress: React.FC<{ isAnimating: boolean }> = ({ isAnimating }) => { 114 | const { animationDuration, isFinished, progress } = useNProgress({ 115 | isAnimating, 116 | }) 117 | 118 | return ( 119 | 120 | 121 | {/* 122 | This example doesn't use a spinner component so the UI stays 123 | tidy. You're free to render whatever is appropriate for your 124 | use-case. 125 | */} 126 | 127 | ) 128 | } 129 | 130 | const AnimationExample: React.FC = () => { 131 | const [isLoading, setIsLoading] = useState(false) 132 | const nodeRef = React.useRef(null) 133 | 134 | return ( 135 | 136 | ( 138 | <> 139 | {/* 140 | Setting a key means that a new NProgress instance is created if 141 | the location is changing, giving us the UI behaviour we want. See: 142 | https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key. 143 | */} 144 | 145 |
    146 | } 150 | /> 151 |
      152 | Red 153 | Green 154 | Blue 155 | Pink 156 |
    157 |
    158 | 159 | {/* 160 | Timeout has been increased by 4x from the original version 161 | for demo purposes. 162 | */} 163 | { 168 | setIsLoading(true) 169 | }} 170 | onEntered={() => { 171 | setIsLoading(false) 172 | }} 173 | timeout={1200} 174 | > 175 | 176 | 177 | 178 |
    Not Found
    } /> 179 |
    180 |
    181 |
    182 |
    183 |
    184 | 185 | )} 186 | /> 187 |
    188 | ) 189 | } 190 | 191 | const container = document.getElementById('root') 192 | const root = createRoot(container!) 193 | root.render() 194 | -------------------------------------------------------------------------------- /examples/react-router-v5/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/react-router-v5/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/react-router-v6/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_DESCRIPTION=$npm_package_description 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/react-router-v6/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /examples/react-router-v6/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress React Router V6 Example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. -------------------------------------------------------------------------------- /examples/react-router-v6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-router-v6", 3 | "description": "ReactNProgress React Router V6 Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "dependencies": { 10 | "@tanem/react-nprogress": "latest", 11 | "@types/jest": "30.0.0", 12 | "@types/node": "22.15.32", 13 | "@types/react": "19.1.8", 14 | "@types/react-dom": "19.1.6", 15 | "@types/react-router-dom": "5.3.3", 16 | "@types/react-transition-group": "4.4.12", 17 | "react": "19.1.0", 18 | "react-dom": "19.1.0", 19 | "react-router-dom": "6.2.1", 20 | "react-scripts": "5.0.1", 21 | "react-transition-group": "4.4.5", 22 | "typescript": "4.9.5" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start" 26 | }, 27 | "browserslist": [ 28 | ">0.2%", 29 | "not dead", 30 | "not ie <= 11", 31 | "not op_mini all" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/react-router-v6/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %REACT_APP_DESCRIPTION% 8 | 9 | 10 | 11 |
    12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-router-v6/src/Bar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Bar: React.FC<{ 4 | animationDuration: number 5 | progress: number 6 | }> = ({ animationDuration, progress }) => ( 7 |
    20 |
    32 |
    33 | ) 34 | 35 | export default Bar 36 | -------------------------------------------------------------------------------- /examples/react-router-v6/src/Container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Container: React.FC<{ 4 | animationDuration: number 5 | isFinished: boolean 6 | }> = ({ animationDuration, children, isFinished }) => ( 7 |
    14 | {children} 15 |
    16 | ) 17 | 18 | export default Container 19 | -------------------------------------------------------------------------------- /examples/react-router-v6/src/index.css: -------------------------------------------------------------------------------- 1 | .fade-enter { 2 | opacity: 0; 3 | z-index: 1; 4 | } 5 | 6 | .fade-enter.fade-enter-active { 7 | opacity: 1; 8 | /* 9 | Duration has been increased by 4x from the original version for demo purposes. 10 | */ 11 | transition: opacity 1000ms ease-in; 12 | } 13 | 14 | @keyframes spinner { 15 | 0% { 16 | transform: rotate(0deg); 17 | } 18 | 100% { 19 | transform: rotate(360deg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-router-v6/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import { useNProgress } from '@tanem/react-nprogress' 4 | import React, { useState } from 'react' 5 | import { createRoot } from 'react-dom/client' 6 | import { 7 | BrowserRouter as Router, 8 | Link, 9 | NavLinkProps, 10 | Route, 11 | Routes, 12 | useLocation, 13 | useParams, 14 | } from 'react-router-dom' 15 | import { CSSTransition, TransitionGroup } from 'react-transition-group' 16 | 17 | import Bar from './Bar' 18 | import Container from './Container' 19 | 20 | const styles: { [key: string]: React.CSSProperties } = {} 21 | 22 | styles.fill = { 23 | bottom: 0, 24 | left: 0, 25 | position: 'absolute', 26 | right: 0, 27 | top: 0, 28 | } 29 | 30 | styles.content = { 31 | ...styles.fill, 32 | textAlign: 'center', 33 | top: '40px', 34 | } 35 | 36 | styles.nav = { 37 | display: 'flex', 38 | height: '40px', 39 | margin: 0, 40 | padding: 0, 41 | position: 'absolute', 42 | top: 0, 43 | width: '100%', 44 | } 45 | 46 | styles.navItem = { 47 | flex: 1, 48 | listStyleType: 'none', 49 | padding: '10px', 50 | textAlign: 'center', 51 | } 52 | 53 | styles.hsl = { 54 | ...styles.fill, 55 | color: 'white', 56 | fontSize: '30px', 57 | paddingTop: '20px', 58 | } 59 | 60 | styles.rgb = { 61 | ...styles.fill, 62 | color: 'white', 63 | fontSize: '30px', 64 | paddingTop: '20px', 65 | } 66 | 67 | const NavLink: React.FC = (props) => ( 68 |
  • 69 | 70 | {props.children} 71 | 72 |
  • 73 | ) 74 | 75 | const HSL = ({ home = false }: { home?: boolean }) => { 76 | const params = useParams() 77 | return ( 78 |
    87 | {home 88 | ? `hsl( 10, 90 %, 50 %)` 89 | : `hsl( 90 | ${params.h}, ${params.s} 91 | %, ${params.l} 92 | %)`} 93 |
    94 | ) 95 | } 96 | 97 | const RGB = () => { 98 | const params = useParams() 99 | return ( 100 |
    107 | rgb( 108 | {params.r}, {params.g}, {params.b}) 109 |
    110 | ) 111 | } 112 | 113 | const Progress: React.FC<{ isAnimating: boolean }> = ({ isAnimating }) => { 114 | const { animationDuration, isFinished, progress } = useNProgress({ 115 | isAnimating, 116 | }) 117 | 118 | return ( 119 | 120 | 121 | {/* 122 | This example doesn't use a spinner component so the UI stays 123 | tidy. You're free to render whatever is appropriate for your 124 | use-case. 125 | */} 126 | 127 | ) 128 | } 129 | 130 | const Home = () => { 131 | const [isLoading, setIsLoading] = useState(false) 132 | const location = useLocation() 133 | const nodeRef = React.useRef(null) 134 | 135 | return ( 136 | <> 137 | {/* 138 | Setting a key means that a new NProgress instance is created if 139 | the location is changing, giving us the UI behaviour we want. See: 140 | https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key. 141 | */} 142 | 143 |
    144 |
      145 | Red 146 | Green 147 | Blue 148 | Pink 149 |
    150 |
    151 | 152 | {/* 153 | Timeout has been increased by 4x from the original version 154 | for demo purposes. 155 | */} 156 | { 161 | setIsLoading(true) 162 | }} 163 | onEntered={() => { 164 | setIsLoading(false) 165 | }} 166 | timeout={1200} 167 | > 168 | 169 | } path="/" /> 170 | } path="/hsl/:h/:s/:l" /> 171 | } path="/rgb/:r/:g/:b" /> 172 | Not Found
    } path="*" /> 173 | 174 | 175 | 176 |
    177 |
    178 | 179 | ) 180 | } 181 | 182 | const AnimationExample: React.FC = () => { 183 | return ( 184 | 185 | 186 | } path="*" /> 187 | 188 | 189 | ) 190 | } 191 | 192 | const container = document.getElementById('root') 193 | const root = createRoot(container!) 194 | root.render() 195 | -------------------------------------------------------------------------------- /examples/react-router-v6/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/react-router-v6/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/render-props/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_DESCRIPTION=$npm_package_description 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/render-props/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /examples/render-props/README.md: -------------------------------------------------------------------------------- 1 | # ReactNProgress Render Props Example 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
    12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.
    15 | You will also see any lint errors in the console. 16 | -------------------------------------------------------------------------------- /examples/render-props/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "render-props", 3 | "description": "ReactNProgress Render Props Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "dependencies": { 10 | "@tanem/react-nprogress": "latest", 11 | "@types/jest": "30.0.0", 12 | "@types/node": "22.15.32", 13 | "@types/react": "19.1.8", 14 | "@types/react-dom": "19.1.6", 15 | "react": "19.1.0", 16 | "react-dom": "19.1.0", 17 | "react-scripts": "5.0.1", 18 | "typescript": "4.9.5" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start" 22 | }, 23 | "browserslist": [ 24 | ">0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /examples/render-props/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %REACT_APP_DESCRIPTION% 8 | 9 | 10 | 11 |
    12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/render-props/src/Bar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Bar: React.FC<{ animationDuration: number; progress: number }> = ({ 4 | progress, 5 | animationDuration, 6 | }) => ( 7 |
    20 |
    32 |
    33 | ) 34 | 35 | export default Bar 36 | -------------------------------------------------------------------------------- /examples/render-props/src/Container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Container: React.FC<{ 4 | animationDuration: number 5 | isFinished: boolean 6 | }> = ({ animationDuration, children, isFinished }) => ( 7 |
    14 | {children} 15 |
    16 | ) 17 | 18 | export default Container 19 | -------------------------------------------------------------------------------- /examples/render-props/src/Progress.tsx: -------------------------------------------------------------------------------- 1 | import { NProgress } from '@tanem/react-nprogress' 2 | import React from 'react' 3 | 4 | import Bar from './Bar' 5 | import Container from './Container' 6 | import Spinner from './Spinner' 7 | 8 | const Progress: React.FC<{ isAnimating: boolean }> = ({ isAnimating }) => ( 9 | 10 | {({ animationDuration, isFinished, progress }) => ( 11 | 12 | 13 | 14 | 15 | )} 16 | 17 | ) 18 | 19 | export default Progress 20 | -------------------------------------------------------------------------------- /examples/render-props/src/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Spinner: React.FC = () => ( 4 |
    13 |
    26 |
    27 | ) 28 | 29 | export default Spinner 30 | -------------------------------------------------------------------------------- /examples/render-props/src/index.css: -------------------------------------------------------------------------------- 1 | @keyframes spinner { 2 | 0% { 3 | transform: rotate(0deg); 4 | } 5 | 100% { 6 | transform: rotate(360deg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/render-props/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React, { useState } from 'react' 4 | import { createRoot } from 'react-dom/client' 5 | 6 | import Progress from './Progress' 7 | 8 | const App: React.FC = () => { 9 | const [state, setState] = useState({ 10 | isAnimating: false, 11 | key: 0, 12 | }) 13 | 14 | return ( 15 | <> 16 | 17 | 27 | 28 | ) 29 | } 30 | 31 | const container = document.getElementById('root') 32 | const root = createRoot(container!) 33 | root.render() 34 | -------------------------------------------------------------------------------- /examples/render-props/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/render-props/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/umd-dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ReactNProgress UMD Dev Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

    14 | Note: UMD builds were removed in React 19. This example uses React 18. 15 |

    16 |
    17 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /examples/umd-dev/main.css: -------------------------------------------------------------------------------- 1 | @keyframes spinner { 2 | 0% { 3 | transform: rotate(0deg); 4 | } 5 | 100% { 6 | transform: rotate(360deg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/umd-dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "umd-dev", 3 | "description": "ReactNProgress UMD Dev Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "main": "index.html", 10 | "scripts": { 11 | "start": "parcel index.html --open", 12 | "build": "parcel build index.html" 13 | }, 14 | "devDependencies": { 15 | "parcel-bundler": "1.12.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/umd-prod/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ReactNProgress UMD Prod Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

    14 | Note: UMD builds were removed in React 19. This example uses React 18. 15 |

    16 |
    17 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /examples/umd-prod/main.css: -------------------------------------------------------------------------------- 1 | @keyframes spinner { 2 | 0% { 3 | transform: rotate(0deg); 4 | } 5 | 100% { 6 | transform: rotate(360deg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/umd-prod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "umd-prod", 3 | "description": "ReactNProgress UMD Prod Example", 4 | "keywords": [ 5 | "@tanem/react-nprogress" 6 | ], 7 | "version": "0.1.0", 8 | "private": true, 9 | "main": "index.html", 10 | "scripts": { 11 | "start": "parcel index.html --open", 12 | "build": "parcel build index.html" 13 | }, 14 | "devDependencies": { 15 | "parcel-bundler": "1.12.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./react-nprogress.cjs.production.js') 5 | } else { 6 | module.exports = require('./react-nprogress.cjs.development.js') 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tanem/react-nprogress", 3 | "version": "5.0.55", 4 | "description": "A React primitive for building slim progress bars.", 5 | "main": "dist/index.js", 6 | "module": "dist/react-nprogress.esm.js", 7 | "jsnext:main": "dist/react-nprogress.esm.js", 8 | "typings": "dist/index.d.ts", 9 | "sideEffects": false, 10 | "files": [ 11 | "dist" 12 | ], 13 | "scripts": { 14 | "authors": "tanem-scripts authors > AUTHORS", 15 | "build": "run-s clean compile bundle", 16 | "bundle": "rollup -c", 17 | "changelog": "tanem-scripts changelog -f v$npm_package_version > CHANGELOG.md", 18 | "check:format": "prettier --list-different \"**/*.{js,ts,tsx}\"", 19 | "check:types": "tsc --noEmit", 20 | "clean": "run-p clean:*", 21 | "clean:compiled": "shx rm -rf compiled", 22 | "clean:coverage": "shx rm -rf coverage", 23 | "clean:dist": "shx rm -rf dist", 24 | "compile": "tsc -p tsconfig.base.json", 25 | "format": "npm run lint -- --fix && prettier --write \"**/*.{js,ts,tsx}\"", 26 | "lint": "eslint .", 27 | "postbundle": "npm run clean:compiled && shx cp ./index.js ./dist/index.js", 28 | "release": "tanem-scripts release", 29 | "test": "run-s check:* lint build test:*", 30 | "test:cjs": "jest --config ./scripts/jest/config.cjs.js", 31 | "test:cjsprod": "jest --config ./scripts/jest/config.cjsprod.js", 32 | "test:es": "jest --config ./scripts/jest/config.es.js", 33 | "test:src": "jest --config ./scripts/jest/config.src.js", 34 | "test:umd": "jest --config ./scripts/jest/config.umd.js", 35 | "test:umdprod": "jest --config ./scripts/jest/config.umdprod.js", 36 | "version": "run-s test changelog authors && git add ." 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/tanem/react-nprogress.git" 41 | }, 42 | "keywords": [ 43 | "animation", 44 | "component", 45 | "higher-order-component", 46 | "hoc", 47 | "hook", 48 | "hooks", 49 | "javascript", 50 | "loading", 51 | "nprogress", 52 | "progress", 53 | "react", 54 | "render-props", 55 | "spinner", 56 | "typescript" 57 | ], 58 | "author": "Tane Morgan ", 59 | "license": "MIT", 60 | "bugs": { 61 | "url": "github:tanem/react-nprogress/issues" 62 | }, 63 | "homepage": "github:tanem/react-nprogress", 64 | "peerDependencies": { 65 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", 66 | "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 67 | }, 68 | "dependencies": { 69 | "@babel/runtime": "^7.27.6", 70 | "hoist-non-react-statics": "^3.3.2" 71 | }, 72 | "devDependencies": { 73 | "@babel/core": "7.27.4", 74 | "@babel/plugin-transform-runtime": "7.27.4", 75 | "@babel/preset-env": "7.27.2", 76 | "@babel/preset-react": "7.27.1", 77 | "@babel/preset-typescript": "7.27.1", 78 | "@eslint/compat": "1.3.0", 79 | "@eslint/eslintrc": "3.3.1", 80 | "@eslint/js": "9.29.0", 81 | "@rollup/plugin-babel": "6.0.4", 82 | "@rollup/plugin-commonjs": "28.0.6", 83 | "@rollup/plugin-node-resolve": "16.0.1", 84 | "@rollup/plugin-replace": "6.0.2", 85 | "@rollup/plugin-terser": "0.4.4", 86 | "@testing-library/react": "16.3.0", 87 | "@types/hoist-non-react-statics": "3.3.6", 88 | "@types/jest": "30.0.0", 89 | "@types/mock-raf": "1.0.6", 90 | "@types/node": "22.15.32", 91 | "@types/react": "19.1.8", 92 | "@types/react-dom": "19.1.6", 93 | "@typescript-eslint/eslint-plugin": "8.34.1", 94 | "@typescript-eslint/parser": "8.34.1", 95 | "babel-core": "6.26.3", 96 | "eslint": "9.29.0", 97 | "eslint-config-prettier": "10.1.5", 98 | "eslint-plugin-react": "7.37.5", 99 | "eslint-plugin-react-hooks": "5.2.0", 100 | "eslint-plugin-simple-import-sort": "12.1.1", 101 | "jest": "30.0.2", 102 | "jest-environment-jsdom": "30.0.2", 103 | "mock-raf": "1.0.1", 104 | "npm-run-all2": "8.0.4", 105 | "prettier": "3.5.3", 106 | "react": "19.1.0", 107 | "react-dom": "19.1.0", 108 | "react-test-renderer": "19.1.0", 109 | "regenerator-runtime": "0.14.1", 110 | "rollup": "4.44.0", 111 | "shx": "0.4.0", 112 | "tanem-scripts": "7.0.27", 113 | "ts-jest": "29.4.0", 114 | "typescript": "5.8.3" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:js-lib", 4 | ":rebaseStalePrs", 5 | ":automergeAll", 6 | ":label(internal)", 7 | ":masterIssue", 8 | ":enableVulnerabilityAlerts" 9 | ], 10 | "ignorePaths": [ 11 | "**/node_modules/**" 12 | ], 13 | "lockFileMaintenance": { 14 | "enabled": true 15 | }, 16 | "packageRules": [ 17 | { 18 | "depTypeList": [ 19 | "dependencies" 20 | ], 21 | "rangeStrategy": "bump" 22 | }, 23 | { 24 | "matchPackageNames": [ 25 | "@types/react-router-dom", 26 | "react-router-dom" 27 | ], 28 | "allowedVersions": "< 6.0.0" 29 | }, 30 | { 31 | "matchPaths": [ 32 | "examples/**/package.json" 33 | ], 34 | "matchPackageNames": [ 35 | "typescript" 36 | ], 37 | "allowedVersions": "< 5.0.0" 38 | } 39 | ], 40 | "stabilityDays": 3 41 | } -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel' 2 | import commonjs from '@rollup/plugin-commonjs' 3 | import nodeResolve from '@rollup/plugin-node-resolve' 4 | import replace from '@rollup/plugin-replace' 5 | import terser from '@rollup/plugin-terser' 6 | 7 | import pkg from './package.json' with { type: 'json' } 8 | 9 | const CJS_DEV = 'CJS_DEV' 10 | const CJS_PROD = 'CJS_PROD' 11 | const ES = 'ES' 12 | const UMD_DEV = 'UMD_DEV' 13 | const UMD_PROD = 'UMD_PROD' 14 | 15 | const input = './compiled/index.js' 16 | const exports = 'named' 17 | 18 | const getExternal = (bundleType) => { 19 | const peerDependencies = Object.keys(pkg.peerDependencies) 20 | const dependencies = Object.keys(pkg.dependencies) 21 | 22 | // Hat-tip: https://github.com/rollup/rollup-plugin-babel/issues/148#issuecomment-399696316. 23 | const makeExternalPredicate = (externals) => { 24 | if (externals.length === 0) { 25 | return () => false 26 | } 27 | const pattern = new RegExp(`^(${externals.join('|')})($|/)`) 28 | return (id) => pattern.test(id) 29 | } 30 | 31 | switch (bundleType) { 32 | case CJS_DEV: 33 | case CJS_PROD: 34 | case ES: 35 | return makeExternalPredicate([...peerDependencies, ...dependencies]) 36 | default: 37 | return makeExternalPredicate(peerDependencies) 38 | } 39 | } 40 | 41 | const isProduction = (bundleType) => 42 | bundleType === CJS_PROD || bundleType === UMD_PROD 43 | 44 | const getBabelConfig = () => ({ 45 | babelHelpers: 'runtime', 46 | babelrc: false, 47 | exclude: 'node_modules/**', 48 | inputSourceMap: true, 49 | plugins: ['@babel/transform-runtime'], 50 | presets: [['@babel/env', { loose: true, modules: false }], '@babel/react'], 51 | }) 52 | 53 | const getPlugins = (bundleType) => [ 54 | nodeResolve(), 55 | commonjs({ 56 | include: 'node_modules/**', 57 | }), 58 | babel(getBabelConfig(bundleType)), 59 | replace({ 60 | preventAssignment: true, 61 | 'process.env.NODE_ENV': JSON.stringify( 62 | isProduction(bundleType) ? 'production' : 'development', 63 | ), 64 | }), 65 | isProduction(bundleType) && 66 | terser({ 67 | compress: { 68 | keep_infinity: true, 69 | pure_getters: true, 70 | }, 71 | ecma: 5, 72 | output: { comments: false }, 73 | toplevel: false, 74 | warnings: true, 75 | }), 76 | ] 77 | 78 | const getCjsConfig = (bundleType) => ({ 79 | external: getExternal(bundleType), 80 | input, 81 | output: { 82 | exports, 83 | file: `dist/react-nprogress.cjs.${ 84 | isProduction(bundleType) ? 'production' : 'development' 85 | }.js`, 86 | format: 'cjs', 87 | sourcemap: true, 88 | }, 89 | plugins: getPlugins(bundleType), 90 | }) 91 | 92 | const getEsConfig = () => ({ 93 | external: getExternal(ES), 94 | input, 95 | output: { 96 | exports, 97 | file: pkg.module, 98 | format: 'es', 99 | sourcemap: true, 100 | }, 101 | plugins: getPlugins(ES), 102 | }) 103 | 104 | const getUmdConfig = (bundleType) => ({ 105 | external: getExternal(bundleType), 106 | input, 107 | output: { 108 | exports, 109 | file: `dist/react-nprogress.umd.${ 110 | isProduction(bundleType) ? 'production' : 'development' 111 | }.js`, 112 | format: 'umd', 113 | globals: { 114 | react: 'React', 115 | 'react-dom': 'ReactDOM', 116 | }, 117 | name: 'NProgress', 118 | sourcemap: true, 119 | }, 120 | plugins: getPlugins(bundleType), 121 | }) 122 | 123 | export default [ 124 | getCjsConfig(CJS_DEV), 125 | getCjsConfig(CJS_PROD), 126 | getEsConfig(), 127 | getUmdConfig(UMD_DEV), 128 | getUmdConfig(UMD_PROD), 129 | ] 130 | -------------------------------------------------------------------------------- /scripts/jest/config.cjs.js: -------------------------------------------------------------------------------- 1 | const srcConfig = require('./config.src') 2 | 3 | module.exports = Object.assign({}, srcConfig, { 4 | collectCoverage: false, 5 | moduleNameMapper: { 6 | '^../src$': `/dist/react-nprogress.cjs.development.js`, 7 | }, 8 | testMatch: ['/test/(use|with)?NProgress.spec.ts?(x)'], 9 | }) 10 | -------------------------------------------------------------------------------- /scripts/jest/config.cjsprod.js: -------------------------------------------------------------------------------- 1 | const srcConfig = require('./config.src') 2 | 3 | module.exports = Object.assign({}, srcConfig, { 4 | collectCoverage: false, 5 | moduleNameMapper: { 6 | '^../src$': `/dist/react-nprogress.cjs.production.js`, 7 | }, 8 | testMatch: ['/test/(use|with)?NProgress.spec.ts?(x)'], 9 | }) 10 | -------------------------------------------------------------------------------- /scripts/jest/config.es.js: -------------------------------------------------------------------------------- 1 | const srcConfig = require('./config.src') 2 | 3 | module.exports = Object.assign({}, srcConfig, { 4 | collectCoverage: false, 5 | moduleNameMapper: { 6 | '^../src$': `/dist/react-nprogress.esm.js`, 7 | }, 8 | testMatch: ['/test/(use|with)?NProgress.spec.ts?(x)'], 9 | }) 10 | -------------------------------------------------------------------------------- /scripts/jest/config.src.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverage: true, 3 | collectCoverageFrom: ['src/*.{ts,tsx}'], 4 | moduleFileExtensions: ['ts', 'tsx', 'js'], 5 | preset: 'ts-jest', 6 | rootDir: process.cwd(), 7 | roots: ['/test'], 8 | testEnvironment: 'jsdom', 9 | testMatch: ['/test/*.spec.ts?(x)'], 10 | transform: { '^.+\\.(js|tsx?)$': 'ts-jest' }, 11 | } 12 | -------------------------------------------------------------------------------- /scripts/jest/config.umd.js: -------------------------------------------------------------------------------- 1 | const srcConfig = require('./config.src') 2 | 3 | module.exports = Object.assign({}, srcConfig, { 4 | collectCoverage: false, 5 | moduleNameMapper: { 6 | '^../src$': `/dist/react-nprogress.umd.development.js`, 7 | }, 8 | testMatch: ['/test/(use|with)?NProgress.spec.ts?(x)'], 9 | }) 10 | -------------------------------------------------------------------------------- /scripts/jest/config.umdprod.js: -------------------------------------------------------------------------------- 1 | const srcConfig = require('./config.src') 2 | 3 | module.exports = Object.assign({}, srcConfig, { 4 | collectCoverage: false, 5 | moduleNameMapper: { 6 | '^../src$': `/dist/react-nprogress.umd.production.js`, 7 | }, 8 | testMatch: ['/test/(use|with)?NProgress.spec.ts?(x)'], 9 | }) 10 | -------------------------------------------------------------------------------- /src/NProgress.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { Options } from './types' 4 | import { useNProgress } from './useNProgress' 5 | 6 | type Props = Options & { 7 | children: (renderProps: ReturnType) => React.ReactElement 8 | } 9 | 10 | export const NProgress: React.FC = ({ 11 | children, 12 | ...restProps 13 | }: Props) => { 14 | const renderProps = useNProgress(restProps) 15 | return children(renderProps) 16 | } 17 | -------------------------------------------------------------------------------- /src/clamp.ts: -------------------------------------------------------------------------------- 1 | export const clamp = (num: number, lower: number, upper: number): number => { 2 | num = num <= upper ? num : upper 3 | num = num >= lower ? num : lower 4 | return num 5 | } 6 | -------------------------------------------------------------------------------- /src/createQueue.ts: -------------------------------------------------------------------------------- 1 | type Next = () => void 2 | type Callback = (next: Next) => void 3 | 4 | export const createQueue = () => { 5 | let isRunning = false 6 | let pending: Callback[] = [] 7 | 8 | const next: Next = () => { 9 | isRunning = true 10 | const cb = pending.shift() 11 | if (cb) { 12 | return cb(next) 13 | } 14 | isRunning = false 15 | } 16 | 17 | const clear = (): void => { 18 | isRunning = false 19 | pending = [] 20 | } 21 | 22 | const enqueue = (cb: Callback): void => { 23 | pending.push(cb) 24 | if (!isRunning && pending.length === 1) { 25 | next() 26 | } 27 | } 28 | 29 | return { 30 | clear, 31 | enqueue, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/createTimeout.ts: -------------------------------------------------------------------------------- 1 | export const createTimeout = () => { 2 | let handle: number | undefined 3 | 4 | const cancel = (): void => { 5 | if (handle) { 6 | window.cancelAnimationFrame(handle) 7 | } 8 | } 9 | 10 | const schedule = (callback: () => void, delay: number): void => { 11 | let deltaTime 12 | let start: number | undefined 13 | const frame: FrameRequestCallback = (time) => { 14 | start = start || time 15 | deltaTime = time - start 16 | if (deltaTime > delay) { 17 | callback() 18 | return 19 | } 20 | handle = window.requestAnimationFrame(frame) 21 | } 22 | 23 | handle = window.requestAnimationFrame(frame) 24 | } 25 | 26 | return { 27 | cancel, 28 | schedule, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-lifecycles-compat' 2 | -------------------------------------------------------------------------------- /src/increment.ts: -------------------------------------------------------------------------------- 1 | import { clamp } from './clamp' 2 | 3 | export const increment = (progress: number): number => { 4 | let amount = 0 5 | 6 | if (progress >= 0 && progress < 0.2) { 7 | amount = 0.1 8 | } else if (progress >= 0.2 && progress < 0.5) { 9 | amount = 0.04 10 | } else if (progress >= 0.5 && progress < 0.8) { 11 | amount = 0.02 12 | } else if (progress >= 0.8 && progress < 0.99) { 13 | amount = 0.005 14 | } 15 | 16 | return clamp(progress + amount, 0, 0.994) 17 | } 18 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './NProgress' 2 | export * from './useNProgress' 3 | export * from './withNProgress' 4 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Options { 2 | animationDuration?: number 3 | incrementDuration?: number 4 | isAnimating?: boolean 5 | minimum?: number 6 | } 7 | -------------------------------------------------------------------------------- /src/useEffectOnce.ts: -------------------------------------------------------------------------------- 1 | // Hat-tip: 2 | // https://github.com/streamich/react-use/blob/master/src/useEffectOnce.ts. 3 | // 4 | // `react-use` appears to be unmaintained, so moving the required code into 5 | // this project for now. 6 | 7 | import { EffectCallback, useEffect } from 'react' 8 | 9 | export const useEffectOnce = (effect: EffectCallback) => { 10 | // eslint-disable-next-line react-hooks/exhaustive-deps 11 | useEffect(effect, []) 12 | } 13 | -------------------------------------------------------------------------------- /src/useGetSetState.ts: -------------------------------------------------------------------------------- 1 | // Hat-tip: 2 | // https://github.com/streamich/react-use/blob/master/src/useGetSetState.ts. 3 | // 4 | // `react-use` appears to be unmaintained, so moving the required code into 5 | // this project for now. 6 | 7 | import { useCallback, useRef, useState } from 'react' 8 | 9 | const incrementParameter = (num: number): number => ++num % 1_000_000 10 | 11 | const useUpdate = () => { 12 | const [, setState] = useState(0) 13 | return useCallback(() => setState(incrementParameter), []) 14 | } 15 | 16 | export const useGetSetState = ( 17 | /* istanbul ignore next */ 18 | initialState: T = {} as T, 19 | ): [() => T, (patch: Partial) => void] => { 20 | const update = useUpdate() 21 | const state = useRef({ ...(initialState as object) } as T) 22 | const get = useCallback(() => state.current, []) 23 | const set = useCallback((patch: Partial) => { 24 | if (!patch) { 25 | return 26 | } 27 | Object.assign(state.current, patch) 28 | update() 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | }, []) 31 | 32 | return [get, set] 33 | } 34 | -------------------------------------------------------------------------------- /src/useNProgress.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef } from 'react' 2 | 3 | import { clamp } from './clamp' 4 | import { createQueue } from './createQueue' 5 | import { createTimeout } from './createTimeout' 6 | import { increment } from './increment' 7 | import { Options } from './types' 8 | import { useEffectOnce } from './useEffectOnce' 9 | import { useGetSetState } from './useGetSetState' 10 | import { useUpdateEffect } from './useUpdateEffect' 11 | 12 | /* istanbul ignore next */ 13 | const noop = () => undefined 14 | 15 | const initialState: { 16 | isFinished: boolean 17 | progress: number 18 | sideEffect: () => void 19 | } = { 20 | isFinished: true, 21 | progress: 0, 22 | sideEffect: noop, 23 | } 24 | 25 | export const useNProgress = ({ 26 | animationDuration = 200, 27 | incrementDuration = 800, 28 | isAnimating = false, 29 | minimum = 0.08, 30 | }: Options = {}): { 31 | animationDuration: number 32 | isFinished: boolean 33 | progress: number 34 | } => { 35 | const [get, setState] = useGetSetState(initialState) 36 | 37 | const queue = useRef | null>(null) 38 | const timeout = useRef | null>(null) 39 | 40 | useEffectOnce(() => { 41 | queue.current = createQueue() 42 | timeout.current = createTimeout() 43 | }) 44 | 45 | const cleanup = useCallback(() => { 46 | timeout.current?.cancel() 47 | queue.current?.clear() 48 | }, []) 49 | 50 | const set = useCallback( 51 | (n: number) => { 52 | n = clamp(n, minimum, 1) 53 | 54 | if (n === 1) { 55 | cleanup() 56 | 57 | queue.current?.enqueue((next) => { 58 | setState({ 59 | progress: n, 60 | sideEffect: () => 61 | timeout.current?.schedule(next, animationDuration), 62 | }) 63 | }) 64 | 65 | queue.current?.enqueue(() => { 66 | setState({ isFinished: true, sideEffect: cleanup }) 67 | }) 68 | 69 | return 70 | } 71 | 72 | queue.current?.enqueue((next) => { 73 | setState({ 74 | isFinished: false, 75 | progress: n, 76 | sideEffect: () => timeout.current?.schedule(next, animationDuration), 77 | }) 78 | }) 79 | }, 80 | [animationDuration, cleanup, minimum, queue, setState, timeout], 81 | ) 82 | 83 | const trickle = useCallback(() => { 84 | set(increment(get().progress)) 85 | }, [get, set]) 86 | 87 | const start = useCallback(() => { 88 | const work = () => { 89 | trickle() 90 | queue.current?.enqueue((next) => { 91 | timeout.current?.schedule(() => { 92 | work() 93 | next() 94 | }, incrementDuration) 95 | }) 96 | } 97 | 98 | work() 99 | }, [incrementDuration, queue, timeout, trickle]) 100 | 101 | const savedTrickle = useRef<() => void>(noop) 102 | 103 | const sideEffect = get().sideEffect 104 | 105 | useEffect(() => { 106 | savedTrickle.current = trickle 107 | }) 108 | 109 | useEffectOnce(() => { 110 | if (isAnimating) { 111 | start() 112 | } 113 | return cleanup 114 | }) 115 | 116 | useUpdateEffect(() => { 117 | get().sideEffect() 118 | }, [get, sideEffect]) 119 | 120 | useUpdateEffect(() => { 121 | if (!isAnimating) { 122 | set(1) 123 | } else { 124 | setState({ 125 | ...initialState, 126 | sideEffect: start, 127 | }) 128 | } 129 | }, [isAnimating, set, setState, start]) 130 | 131 | return { 132 | animationDuration, 133 | isFinished: get().isFinished, 134 | progress: get().progress, 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/useUpdateEffect.ts: -------------------------------------------------------------------------------- 1 | // Hat-tip: 2 | // https://github.com/streamich/react-use/blob/master/src/useUpdateEffect.ts. 3 | // 4 | // `react-use` appears to be unmaintained, so moving the required code into 5 | // this project for now. 6 | 7 | import { useEffect, useRef } from 'react' 8 | 9 | const useFirstMountState = (): boolean => { 10 | const isFirst = useRef(true) 11 | 12 | if (isFirst.current) { 13 | isFirst.current = false 14 | return true 15 | } 16 | 17 | return isFirst.current 18 | } 19 | 20 | export const useUpdateEffect: typeof useEffect = (effect, deps) => { 21 | const isFirstMount = useFirstMountState() 22 | 23 | useEffect(() => { 24 | if (!isFirstMount) { 25 | return effect() 26 | } 27 | // eslint-disable-next-line react-hooks/exhaustive-deps 28 | }, deps) 29 | } 30 | -------------------------------------------------------------------------------- /src/withNProgress.tsx: -------------------------------------------------------------------------------- 1 | import hoistNonReactStatics from 'hoist-non-react-statics' 2 | import * as React from 'react' 3 | 4 | import { Options } from './types' 5 | import { useNProgress } from './useNProgress' 6 | 7 | type Outer

    = P & Options 8 | 9 | type Inner

    = P & ReturnType 10 | 11 | export function withNProgress

    ( 12 | BaseComponent: React.ComponentType>, 13 | ): React.FC> { 14 | const WithNProgress: React.FC> = (props) => { 15 | const hookProps = useNProgress(props) 16 | return 17 | } 18 | 19 | hoistNonReactStatics(WithNProgress, BaseComponent) 20 | 21 | return WithNProgress 22 | } 23 | -------------------------------------------------------------------------------- /test/NProgress.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react' 2 | import * as React from 'react' 3 | 4 | import { NProgress } from '../src' 5 | 6 | test('receives render props', () => { 7 | let animationDuration 8 | let isFinished 9 | let progress 10 | 11 | render( 12 | 13 | {(props) => { 14 | animationDuration = props.animationDuration 15 | isFinished = props.isFinished 16 | progress = props.progress 17 | return <> 18 | }} 19 | , 20 | ) 21 | 22 | expect(animationDuration).toBe(200) 23 | expect(isFinished).toBe(true) 24 | expect(progress).toBe(0) 25 | }) 26 | -------------------------------------------------------------------------------- /test/clamp.spec.ts: -------------------------------------------------------------------------------- 1 | import { clamp } from '../src/clamp' 2 | 3 | test('clamps between lower and upper', () => { 4 | expect(clamp(-1, 0, 1)).toBe(0) 5 | expect(clamp(0.5, 0, 1)).toBe(0.5) 6 | expect(clamp(2, 0, 1)).toBe(1) 7 | }) 8 | -------------------------------------------------------------------------------- /test/increment.spec.ts: -------------------------------------------------------------------------------- 1 | import { increment } from '../src/increment' 2 | 3 | test('increments corrrectly', () => { 4 | expect(increment(-1)).toBeCloseTo(0) 5 | expect(increment(0)).toBeCloseTo(0.1) 6 | expect(increment(0.2)).toBeCloseTo(0.24) 7 | expect(increment(0.5)).toBeCloseTo(0.52) 8 | expect(increment(0.8)).toBeCloseTo(0.805) 9 | expect(increment(1)).toBeCloseTo(0.994) 10 | }) 11 | -------------------------------------------------------------------------------- /test/queue.spec.ts: -------------------------------------------------------------------------------- 1 | import { createQueue } from '../src/createQueue' 2 | 3 | const { clear, enqueue } = createQueue() 4 | 5 | jest.useFakeTimers() 6 | 7 | test('starts running when the first callback is pushed onto the queue', () => { 8 | const mockFn = jest.fn() 9 | 10 | enqueue((next) => { 11 | mockFn() 12 | next() 13 | }) 14 | 15 | expect(mockFn).toHaveBeenCalled() 16 | }) 17 | 18 | test('executes callbacks in the queue sequentially', () => { 19 | const mockFn = jest.fn((str) => str) 20 | 21 | enqueue((next) => { 22 | setTimeout(() => { 23 | mockFn('first') 24 | next() 25 | }, 0) 26 | }) 27 | enqueue((next) => { 28 | mockFn('second') 29 | next() 30 | }) 31 | 32 | jest.runAllTimers() 33 | 34 | expect(mockFn).toHaveBeenNthCalledWith(1, 'first') 35 | expect(mockFn).toHaveBeenNthCalledWith(2, 'second') 36 | }) 37 | 38 | test('can clear the queue', () => { 39 | const mockFn = jest.fn() 40 | 41 | enqueue((next) => setTimeout(next, 0)) 42 | enqueue((next) => { 43 | mockFn() 44 | next() 45 | }) 46 | 47 | clear() 48 | jest.runAllTimers() 49 | 50 | expect(mockFn).not.toHaveBeenCalled() 51 | }) 52 | -------------------------------------------------------------------------------- /test/timeout.spec.ts: -------------------------------------------------------------------------------- 1 | import createMockRaf from 'mock-raf' 2 | 3 | import { createTimeout } from '../src/createTimeout' 4 | 5 | const { cancel, schedule } = createTimeout() 6 | 7 | const mockRaf = createMockRaf() 8 | 9 | window.requestAnimationFrame = mockRaf.raf 10 | window.cancelAnimationFrame = mockRaf.cancel 11 | 12 | test('executes a callback after a delay', () => { 13 | const mockFn = jest.fn() 14 | schedule(mockFn, 10) 15 | mockRaf.step() 16 | mockRaf.step() 17 | expect(mockFn).toHaveBeenCalled() 18 | }) 19 | 20 | test('can cancel a pending callback', () => { 21 | const mockFn = jest.fn() 22 | schedule(mockFn, 10) 23 | mockRaf.step() 24 | cancel() 25 | mockRaf.step() 26 | expect(mockFn).not.toHaveBeenCalled() 27 | }) 28 | -------------------------------------------------------------------------------- /test/useEffectOnce.spec.ts: -------------------------------------------------------------------------------- 1 | // Hat-tip: 2 | // https://github.com/streamich/react-use/blob/master/tests/useEffectOnce.test.ts. 3 | // 4 | // `react-use` appears to be unmaintained, so moving the required code into 5 | // this project for now. 6 | 7 | import { renderHook } from '@testing-library/react' 8 | 9 | import { useEffectOnce } from '../src/useEffectOnce' 10 | 11 | it('should run provided effect only once', () => { 12 | const mockEffect = jest.fn() 13 | const { rerender } = renderHook(() => useEffectOnce(mockEffect)) 14 | expect(mockEffect).toHaveBeenCalledTimes(1) 15 | rerender() 16 | expect(mockEffect).toHaveBeenCalledTimes(1) 17 | }) 18 | 19 | it('should run clean-up provided on unmount', () => { 20 | const mockCleanup = jest.fn() 21 | const { unmount } = renderHook(() => useEffectOnce(() => mockCleanup)) 22 | expect(mockCleanup).not.toHaveBeenCalled() 23 | unmount() 24 | expect(mockCleanup).toHaveBeenCalledTimes(1) 25 | }) 26 | -------------------------------------------------------------------------------- /test/useGetSetState.spec.ts: -------------------------------------------------------------------------------- 1 | // Hat-tip: 2 | // https://github.com/streamich/react-use/blob/master/tests/useGetSetState.test.ts. 3 | // 4 | // `react-use` appears to be unmaintained, so moving the required code into 5 | // this project for now. 6 | 7 | import { act, renderHook } from '@testing-library/react' 8 | 9 | import { useGetSetState } from '../src/useGetSetState' 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 12 | const setUp = (initialState: any) => 13 | renderHook(() => useGetSetState(initialState)) 14 | 15 | beforeEach(() => { 16 | jest.useFakeTimers() 17 | }) 18 | 19 | it('should init getter and setter', () => { 20 | const { result } = setUp({ foo: 'initialValue' }) 21 | const [get, set] = result.current 22 | 23 | expect(get).toBeInstanceOf(Function) 24 | expect(set).toBeInstanceOf(Function) 25 | }) 26 | 27 | it('should get current state', () => { 28 | const { result } = setUp({ bar: 'z', foo: 'a' }) 29 | const [get] = result.current 30 | 31 | const currentState = get() 32 | 33 | expect(currentState).toEqual({ bar: 'z', foo: 'a' }) 34 | }) 35 | 36 | it('should set new state by applying patch with existing keys', () => { 37 | const { result } = setUp({ bar: 'z', foo: 'a' }) 38 | const [get, set] = result.current 39 | 40 | act(() => set({ bar: 'y' })) 41 | 42 | const currentState = get() 43 | expect(currentState).toEqual({ bar: 'y', foo: 'a' }) 44 | }) 45 | 46 | it('should set new state by applying patch with new keys', () => { 47 | const { result } = setUp({ bar: 'z', foo: 'a' }) 48 | const [get, set] = result.current 49 | 50 | act(() => set({ qux: 'f' })) 51 | 52 | const currentState = get() 53 | expect(currentState).toEqual({ bar: 'z', foo: 'a', qux: 'f' }) 54 | }) 55 | 56 | it('should set new state by applying patch with both new and old keys', () => { 57 | const { result } = setUp({ bar: 'z', foo: 'a' }) 58 | const [get, set] = result.current 59 | 60 | act(() => set({ bar: 'y', qux: 'f' })) 61 | 62 | const currentState = get() 63 | expect(currentState).toEqual({ bar: 'y', foo: 'a', qux: 'f' }) 64 | }) 65 | 66 | it('should NOT set new state if empty patch received', () => { 67 | const { result } = setUp({ bar: 'z', foo: 'a' }) 68 | const [get, set] = result.current 69 | 70 | act(() => set({})) 71 | 72 | const currentState = get() 73 | expect(currentState).toEqual({ bar: 'z', foo: 'a' }) 74 | }) 75 | 76 | it('should NOT set new state if no patch received', () => { 77 | const { result } = setUp({ bar: 'z', foo: 'a' }) 78 | const [get, set] = result.current 79 | 80 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 81 | // @ts-ignore 82 | act(() => set()) 83 | 84 | const currentState = get() 85 | expect(currentState).toEqual({ bar: 'z', foo: 'a' }) 86 | }) 87 | 88 | it('should get and set expected state when used in nested functions', () => { 89 | const onClick = jest.fn(() => { 90 | setTimeout(() => { 91 | set({ counter: get().counter + 1 }) 92 | }, 1000) 93 | }) 94 | 95 | const { result } = setUp({ counter: 0 }) 96 | const [get, set] = result.current 97 | 98 | // Simulate 3 clicks. 99 | onClick() 100 | onClick() 101 | onClick() 102 | 103 | // Fast-forward until all timers have been executed. 104 | act(() => { 105 | jest.runAllTimers() 106 | }) 107 | 108 | const currentState = get() 109 | expect(currentState).toEqual({ counter: 3 }) 110 | expect(onClick).toHaveBeenCalledTimes(3) 111 | }) 112 | -------------------------------------------------------------------------------- /test/useNProgress.spec.ts: -------------------------------------------------------------------------------- 1 | import { act, renderHook } from '@testing-library/react' 2 | import createMockRaf from 'mock-raf' 3 | 4 | import { useNProgress } from '../src' 5 | 6 | const mockRaf = createMockRaf() 7 | window.requestAnimationFrame = mockRaf.raf 8 | window.cancelAnimationFrame = mockRaf.cancel 9 | 10 | test('defaults', () => { 11 | const { result, unmount } = renderHook(() => useNProgress()) 12 | 13 | expect(result.current).toEqual({ 14 | animationDuration: 200, 15 | isFinished: true, 16 | progress: 0, 17 | }) 18 | 19 | unmount() 20 | }) 21 | 22 | test('starts animating when isAnimating is true', () => { 23 | const { result, unmount } = renderHook(() => 24 | useNProgress({ isAnimating: true }), 25 | ) 26 | 27 | expect(result.current).toEqual({ 28 | animationDuration: 200, 29 | isFinished: false, 30 | progress: 0.1, 31 | }) 32 | 33 | unmount() 34 | }) 35 | 36 | test('starts animating when isAnimating changes from false to true', () => { 37 | const { result, rerender, unmount } = renderHook( 38 | ({ isAnimating }) => useNProgress({ isAnimating }), 39 | { initialProps: { isAnimating: false } }, 40 | ) 41 | 42 | rerender({ isAnimating: true }) 43 | 44 | expect(result.current).toEqual({ 45 | animationDuration: 200, 46 | isFinished: false, 47 | progress: 0.1, 48 | }) 49 | 50 | unmount() 51 | }) 52 | 53 | test('increments correctly', () => { 54 | const { result, unmount } = renderHook(() => 55 | useNProgress({ isAnimating: true }), 56 | ) 57 | 58 | act(() => { 59 | mockRaf.step() 60 | mockRaf.step({ time: 201 }) 61 | mockRaf.step() 62 | mockRaf.step({ time: 801 }) 63 | }) 64 | 65 | expect(result.current).toEqual({ 66 | animationDuration: 200, 67 | isFinished: false, 68 | progress: 0.2, 69 | }) 70 | 71 | unmount() 72 | }) 73 | 74 | test('animates to finish if isAnimating was changed from true to false', () => { 75 | const { result, rerender, unmount } = renderHook( 76 | ({ isAnimating }) => useNProgress({ isAnimating }), 77 | { initialProps: { isAnimating: true } }, 78 | ) 79 | 80 | rerender({ isAnimating: false }) 81 | 82 | act(() => { 83 | mockRaf.step() 84 | mockRaf.step({ time: 201 }) 85 | }) 86 | 87 | expect(result.current).toEqual({ 88 | animationDuration: 200, 89 | isFinished: true, 90 | progress: 1, 91 | }) 92 | 93 | unmount() 94 | }) 95 | 96 | test('correctly restarts a finished animation', () => { 97 | const { result, rerender, unmount } = renderHook( 98 | ({ isAnimating }) => useNProgress({ isAnimating }), 99 | { initialProps: { isAnimating: true } }, 100 | ) 101 | 102 | rerender({ isAnimating: false }) 103 | 104 | act(() => { 105 | mockRaf.step() 106 | mockRaf.step({ time: 201 }) 107 | }) 108 | 109 | expect(result.current).toEqual({ 110 | animationDuration: 200, 111 | isFinished: true, 112 | progress: 1, 113 | }) 114 | 115 | rerender({ isAnimating: true }) 116 | 117 | act(() => { 118 | mockRaf.step() 119 | mockRaf.step({ time: 201 }) 120 | mockRaf.step() 121 | mockRaf.step({ time: 801 }) 122 | }) 123 | 124 | expect(result.current).toEqual({ 125 | animationDuration: 200, 126 | isFinished: false, 127 | progress: 0.2, 128 | }) 129 | 130 | unmount() 131 | }) 132 | -------------------------------------------------------------------------------- /test/useUpdateEffect.spec.ts: -------------------------------------------------------------------------------- 1 | // Hat-tip: 2 | // https://github.com/streamich/react-use/blob/master/tests/useUpdateEffect.test.ts. 3 | // 4 | // `react-use` appears to be unmaintained, so moving the required code into 5 | // this project for now. 6 | 7 | import { renderHook } from '@testing-library/react' 8 | 9 | import { useUpdateEffect } from '../src/useUpdateEffect' 10 | 11 | it('should run effect on update', () => { 12 | const mockEffect = jest.fn() 13 | const { rerender } = renderHook(() => useUpdateEffect(mockEffect)) 14 | expect(mockEffect).not.toHaveBeenCalled() 15 | rerender() 16 | expect(mockEffect).toHaveBeenCalledTimes(1) 17 | }) 18 | 19 | it('should run cleanup on unmount', () => { 20 | const mockCleanup = jest.fn() 21 | const hook = renderHook(() => useUpdateEffect(() => mockCleanup)) 22 | hook.rerender() 23 | hook.unmount() 24 | expect(mockCleanup).toHaveBeenCalledTimes(1) 25 | }) 26 | -------------------------------------------------------------------------------- /test/withNProgress.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react' 2 | import * as React from 'react' 3 | 4 | import { withNProgress } from '../src' 5 | 6 | test('wrapped component receives props', () => { 7 | let animationDuration 8 | let isFinished 9 | let progress 10 | 11 | const EnhancedComponent = withNProgress((props) => { 12 | animationDuration = props.animationDuration 13 | isFinished = props.isFinished 14 | progress = props.progress 15 | return <> 16 | }) 17 | 18 | render() 19 | 20 | expect(animationDuration).toBe(200) 21 | expect(isFinished).toBe(true) 22 | expect(progress).toBe(0) 23 | }) 24 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "declarationDir": "dist", 6 | "esModuleInterop": true, 7 | "jsx": "react", 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "outDir": "compiled", 15 | "strict": true, 16 | "target": "esnext" 17 | }, 18 | "exclude": ["node_modules", "dist"], 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true 6 | }, 7 | "include": ["examples", "scripts", "src", "test"], 8 | "files": ["eslint.config.mjs", "index.js", "rollup.config.mjs"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["src", "test"], 4 | "compilerOptions": { 5 | "allowJs": true, 6 | "declaration": false, 7 | "declarationDir": null, 8 | "module": "commonjs" 9 | } 10 | } 11 | --------------------------------------------------------------------------------