├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── actions │ └── setup-node │ │ └── action.yaml ├── dependabot.yml └── workflows │ └── deploy.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.cjs ├── .storybook ├── canonical-link │ └── register.js ├── google-analytics-v4 │ └── register.js ├── main.js ├── manager.js └── preview.js ├── .vscode └── settings.json ├── .yarn └── releases │ └── yarn-4.9.1.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── eslint.config.mjs ├── examples ├── components │ ├── Code.tsx │ ├── ColorPicker.tsx │ ├── LoaderItem.tsx │ └── index.ts ├── index.html ├── index.tsx └── styles.css ├── jest.config.js ├── package.json ├── scripts ├── build-docs.sh ├── mod.rb ├── stories.rb └── stories.template ├── src ├── BarLoader.tsx ├── BeatLoader.tsx ├── BounceLoader.tsx ├── CircleLoader.tsx ├── ClimbingBoxLoader.tsx ├── ClipLoader.tsx ├── ClockLoader.tsx ├── DotLoader.tsx ├── FadeLoader.tsx ├── GridLoader.tsx ├── HashLoader.tsx ├── MoonLoader.tsx ├── PacmanLoader.tsx ├── PropagateLoader.tsx ├── PuffLoader.tsx ├── PulseLoader.tsx ├── RingLoader.tsx ├── RiseLoader.tsx ├── RotateLoader.tsx ├── ScaleLoader.tsx ├── SkewLoader.tsx ├── SquareLoader.tsx ├── SyncLoader.tsx ├── helpers │ ├── animation.server.test.ts │ ├── animation.test.ts │ ├── animation.ts │ ├── colors.test.ts │ ├── colors.ts │ ├── props.ts │ ├── unitConverter.test.ts │ └── unitConverter.ts └── index.ts ├── stories └── .keep ├── test-apps └── nextjs │ ├── .gitignore │ ├── next.config.ts │ ├── package.json │ ├── src │ └── app │ │ ├── layout.tsx │ │ └── page.tsx │ ├── tsconfig.json │ └── yarn.lock ├── tests └── AllLoaders.test.tsx ├── tsconfig.cjs.json ├── tsconfig.esm.json ├── tsconfig.json ├── vite.config.mjs └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Package Version** 10 | 11 | 12 | 13 | **Describe the bug** 14 | 15 | 16 | **To Reproduce** 17 | 18 | 19 | **Expected behavior** 20 | 21 | 22 | 23 | **Screenshots** 24 | 25 | 26 | 27 | **Additional context** 28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | 17 | **Describe alternatives you've considered** 18 | 19 | 20 | 21 | **Additional context** 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # What changes are introduced? 2 | 3 | # Any screenshots? 4 | -------------------------------------------------------------------------------- /.github/actions/setup-node/action.yaml: -------------------------------------------------------------------------------- 1 | name: setup-node-env 2 | 3 | runs: 4 | using: "composite" 5 | steps: 6 | - uses: actions/setup-node@v4 7 | with: 8 | node-version: 20.x 9 | - run: npm install --global yarn 10 | shell: bash 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: 20.x 14 | cache: "yarn" 15 | - run: yarn install 16 | shell: bash 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "github-actions" 13 | directory: "/.github/actions/setup-node" 14 | schedule: 15 | interval: "daily" 16 | - package-ecosystem: "npm" 17 | directory: "/" 18 | schedule: 19 | interval: "daily" 20 | groups: 21 | storybook: 22 | patterns: ["@storybook/*", "storybook"] 23 | testing: 24 | patterns: ["@testing-library/*", "jest*", "ts-jest"] 25 | eslint: 26 | patterns: ["eslint*", "@typescript-eslint/*"] 27 | react: 28 | patterns: ["react", "react-dom", "@types/react", "@types/react-dom"] 29 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | jobs: 15 | tests: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: ./.github/actions/setup-node 20 | - run: yarn run clean 21 | - run: yarn run test 22 | - name: Coveralls 23 | uses: coverallsapp/github-action@master 24 | with: 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | lint: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: ./.github/actions/setup-node 32 | - run: yarn run clean 33 | - run: yarn run lint --max-warnings=0 34 | 35 | build-site: 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: ruby/setup-ruby@v1 41 | with: 42 | ruby-version: "3.0" 43 | - uses: ./.github/actions/setup-node 44 | - name: Build demo site 45 | run: yarn run build:demo 46 | - name: Create stories 47 | run: ruby scripts/stories.rb 48 | - name: Build storybook 49 | run: yarn run build-storybook 50 | 51 | - name: Upload Pages Artifact 52 | if: ${{ github.event_name != 'pull_request' }} 53 | uses: actions/upload-pages-artifact@v3 54 | with: 55 | path: "./dist" 56 | 57 | deploy-site: 58 | if: ${{ github.event_name != 'pull_request' }} 59 | needs: [build-site, lint, tests] 60 | runs-on: ubuntu-latest 61 | environment: 62 | name: github-pages 63 | url: ${{ steps.deployment.outputs.page_url }} 64 | steps: 65 | - name: Deploy to GitHub Pages 66 | id: deployment 67 | uses: actions/deploy-pages@v4 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.iml 4 | .*.haste_cache.* 5 | .DS_Store 6 | .idea 7 | npm-debug.log 8 | node_modules 9 | dist 10 | docs/javascripts 11 | docs/js 12 | docs/index.html 13 | docs/storybook 14 | coverage 15 | cjs 16 | esm 17 | umd 18 | .yarn/cache 19 | .yarn/install-state.gz 20 | stories/*.tsx 21 | storybook-static 22 | coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.iml 4 | .*.haste_cache.* 5 | .DS_Store 6 | .idea 7 | .babelrc 8 | .eslintrc 9 | npm-debug.log 10 | lib 11 | 12 | .yarn/* 13 | .yarnrc 14 | examples 15 | docs 16 | webpack.config.* 17 | prettier.config.* 18 | template.html 19 | .github 20 | .circleci 21 | .eslintrc.* 22 | coverage 23 | src 24 | tslint.json 25 | jest.config.* 26 | CODEOWNERS 27 | CODE_OF_CONDUCT.md 28 | CONTRIBUTING.md 29 | stories 30 | yarn-error.log 31 | .storybook 32 | .prettierignore 33 | test-apps 34 | tests 35 | CHANGELOG.md 36 | .yarnrc.yml 37 | .prettierrc.cjs 38 | tests/* 39 | scripts/* 40 | .vscode/* 41 | eslint.config.mjs -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .yarn 2 | node_modules 3 | yarn.lock 4 | coverage 5 | docs/storybook 6 | docs/javascripts 7 | docs/js 8 | template.html 9 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Options} */ 2 | module.exports = { 3 | printWidth: 100, 4 | tabWidth: 2, 5 | trailingComma: "es5", 6 | singleQuote: false, 7 | semi: true, 8 | pluginSearchDirs: false, 9 | }; 10 | -------------------------------------------------------------------------------- /.storybook/canonical-link/register.js: -------------------------------------------------------------------------------- 1 | import { addons } from "@storybook/addons"; 2 | 3 | addons.register("storybook/canonical-link", (api) => { 4 | var link = document.createElement("link"); 5 | link.setAttribute("rel", "canonical"); 6 | link.setAttribute("href", "https://www.davidhu.io/react-spinners/storybook"); 7 | document.head.appendChild(link); 8 | }); 9 | -------------------------------------------------------------------------------- /.storybook/google-analytics-v4/register.js: -------------------------------------------------------------------------------- 1 | import { addons } from "@storybook/addons"; 2 | import { STORY_CHANGED, STORY_ERRORED, STORY_MISSING } from "@storybook/core-events"; 3 | import ReactGa from "react-ga4"; 4 | 5 | addons.register("storybook/google-analytics-v4", (api) => { 6 | ReactGa.initialize(window.STORYBOOK_GA_V4_ID, window.STORYBOOK_REACT_GA_OPTIONS); 7 | 8 | api.on(STORY_CHANGED, () => { 9 | const { path } = api.getUrlState(); 10 | ReactGa.send({ hitType: "pageview", page: path }); 11 | }); 12 | api.on(STORY_ERRORED, ({ description }) => { 13 | ReactGa.exception({ 14 | description, 15 | fatal: true, 16 | }); 17 | }); 18 | api.on(STORY_MISSING, (id) => { 19 | ReactGa.exception({ 20 | description: `attempted to render ${id}, but it is missing`, 21 | fatal: false, 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ["../stories/*.stories.@(ts|tsx)"], 3 | 4 | addons: [ 5 | "@storybook/addon-links", 6 | "@storybook/addon-essentials", 7 | "@storybook/addon-interactions", 8 | "storybook-dark-mode", 9 | "./google-analytics-v4/register.js", 10 | "./canonical-link/register.js", 11 | "@storybook/addon-webpack5-compiler-swc", 12 | ], 13 | 14 | framework: { 15 | name: "@storybook/react-vite", 16 | options: {}, 17 | }, 18 | 19 | core: { 20 | disableTelemetry: true, 21 | }, 22 | 23 | docs: {}, 24 | 25 | typescript: { 26 | reactDocgen: "react-docgen-typescript", 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | window.STORYBOOK_GA_ID = "UA-92266369-2"; 2 | window.STORYBOOK_GA_V4_ID = "G-ZLGVJTEW5V"; 3 | window.STORYBOOK_REACT_GA_OPTIONS = {}; 4 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | controls: { 3 | matchers: { 4 | color: /(background|color)$/i, 5 | date: /Date$/, 6 | }, 7 | }, 8 | layout: "centered", 9 | }; 10 | 11 | export const argTypes = { 12 | color: { 13 | description: "Hex code of load colors", 14 | control: { type: "color" }, 15 | defaultValue: "#36d7b7", 16 | }, 17 | loading: { 18 | description: "controls whether loader is shown", 19 | control: { type: "boolean" }, 20 | }, 21 | speedMultiplier: { 22 | description: "controls the speed of animation. Higher number equals faster speed", 23 | control: { type: "number" }, 24 | }, 25 | cssOverride: { 26 | description: "override default styles. Needs to be camelCase keys", 27 | control: { type: "object" }, 28 | }, 29 | }; 30 | export const tags = ["autodocs"]; 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "[ruby]": { 4 | "editor.defaultFormatter": "mbessey.vscode-rufo" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | nodeLinker: node-modules 6 | 7 | yarnPath: .yarn/releases/yarn-4.9.1.cjs 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > This changelog is deprecated. Please check [GitHub Releases](https://github.com/davidhu2000/react-spinners/releases) for the most up-to-date release information. 3 | 4 | # Change Log 5 | 6 | All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## 0.14.1 9 | 10 | - revert [#602](https://github.com/davidhu2000/react-spinners/pull/602) due to issues with test and server side rendering 11 | 12 | ## 0.14.0 13 | 14 | - feat: color prop can accept rgb colors [#586](https://github.com/davidhu2000/react-spinners/pull/586) 15 | - fix: multiple hash loader with different color renders as the same color [#602](https://github.com/davidhu2000/react-spinners/pull/602) 16 | - fix: moon loader wobble if size is not divisible by 7 [#603](https://github.com/davidhu2000/react-spinners/pull/603) 17 | 18 | ## 0.13.8 19 | 20 | - **bugfix**: Remove Animation Fill Mode from CircleLoader to fix SSR mismatch style error. [#558](https://github.com/davidhu2000/react-spinners/pull/558) 21 | 22 | ## 0.13.7 23 | 24 | - **bugfix**: fix PacmanLoader container height/width to adjust with size prop 25 | 26 | ## 0.13.6 27 | 28 | - Improve formatting of example code to include `data-testid` prop 29 | 30 | ## 0.13.5 31 | 32 | - Improve README to include additional available props via span tag 33 | 34 | ## 0.13.4 35 | 36 | - **bugfix**: fix server side render issue on `HashLoader` 37 | 38 | ## 0.13.3 39 | 40 | - **bugfix**: Fix PuffLoader initial rendering issue 41 | 42 | ## 0.13.2 43 | 44 | - remove next version badge until needed 45 | 46 | ## 0.13.1 47 | 48 | - update homepage in package.json 49 | 50 | ## 0.13.0 51 | 52 | - Rewrite each loader from the ground up using functional components. 53 | - Replaced `@emotion` with vanilla javascript and inline style to reduce component size by 75%. This project now have 0 dependencies, while continuing to support server side rendering. 54 | - Added support for custom props such as `aria-label` 55 | - renamed `css` prop to `cssOverride` to avoid type conflicts with css-in-js libraries. 56 | 57 | ## 0.13.0-beta.7 58 | 59 | - **bugfix**: fix style warnings on ClipLoader and CircleLoader 60 | 61 | ## 0.13.0-beta.6 62 | 63 | - **bugfix**: fix GridLoader rendering issue 64 | 65 | ## 0.13.0-beta.5 66 | 67 | - **BREAKING CHANGE**: `css` prop has been renamed to `cssOverride` to avoid type conflicts with css-in-js libraries such as emotion and styled-components 68 | 69 | ## 0.13.0-beta.4 70 | 71 | - **bugfix**: Fix `document is not defined` when rendering server side 72 | 73 | ## 0.13.0-beta.3 74 | 75 | - update `.npmignore` to ignore `stories` folder, yarn error log 76 | 77 | ## 0.13.0-beta.2 78 | 79 | - **bugfix**: Update `tsconfig.json` to ignore `stories` folder. This caused the outputted files to not be in the root directory and breaking the imports. 80 | 81 | ## 0.13.0-beta.1 82 | 83 | - **bugfix**: Properly assign important tag to `GridLoader` width prop. 84 | 85 | ## 0.13.0-alpha.5 86 | 87 | - **bugfix**: Update `GridLoader` height/width with `important` tag to prevent overwrites from outside css. 88 | 89 | ## 0.13.0-alpha.4 90 | 91 | - **Feature**: Add support for custom props in all loaders. 92 | - **Feature**: Removed `@emotion/react` as a dependency. 93 | - **Feature**: Update `RiseLoader` rise amount of use `size` prop instead of hardcoded as 30px. 94 | 95 | ## 0.13.0-alpha.3 96 | 97 | - **Feature**: Add support for custom props in BarLoader 98 | 99 | ## 0.13.0-alpha.2 100 | 101 | - Added react testing library 102 | - added basic tests for BarLoader 103 | - **bugfix**: add `display: inherit` on barloader to fix issue where nothing shows up on page. 104 | 105 | ## 0.13.0-alpha.1 106 | 107 | - Rewrite BarLoader as functional component. Use vanilla javascript to inject keyframes, and removing emotion from the component. 108 | 109 | ## 0.12.0 110 | 111 | - **Feature**: output commonjs, es module, and umd file types. 112 | - **Feature**: add support for react 18 [#464](https://github.com/davidhu2000/react-spinners/pull/464) 113 | 114 | ## 0.12.0-beta.1 115 | 116 | - reverted devDepencies react back to v17 until tests can be migrated away from enzyme. [#471](https://github.com/davidhu2000/react-spinners/pull/471) 117 | 118 | ## 0.12.0-alpha.3 119 | 120 | - migrate from circle-ci to github actions for lint/jest 121 | - **Feature**: add support for react 18 [#464](https://github.com/davidhu2000/react-spinners/pull/464) 122 | 123 | ## 0.12.0-alpha.2 124 | 125 | - Update pragma to `/** @jsxImportSource @emotion/react */` to fix issue with the new jsx runtime. 126 | - update all dependencies to latest version and rebuild demo site 127 | 128 | ## 0.12.0-alpha.1 129 | 130 | - **Feature**: output commonjs, es module, and umd file types. 131 | 132 | ## 0.11.0 133 | 134 | - **Feature**: added `speedMultiplier` prop to allow controlling the speed of animations. 135 | 136 | ## 0.11.0-beta.1 137 | 138 | - No changes, just promoting to beta 139 | 140 | ## 0.11.0-alpha.8 141 | 142 | - Update readme to include speed multiplier prop 143 | 144 | ## 0.11.0-alpha.7 145 | 146 | - Implemented `speedMultiplier` props to all loaders 147 | - Added feature flag to demo site. adding a url param `speed-multiplier=true` will enable to input 148 | 149 | ## 0.11.0-alpha.6 150 | 151 | - Refactored all the tests using shared specs to reduce maintenance cost. 152 | - Removed unnecessary type in `colors.ts` to let typescript infer. 153 | 154 | ## 0.11.0-alpha.5 155 | 156 | - Implement `speedMultipler` prop to `PulseLoader`. This is done to test the API for a single loader before adding to the rest. 157 | 158 | ## 0.11.0-alpha.4 159 | 160 | - Clean up `BarLoader` by marking the props using `Required` utility to avoid having to do `width || Loader.defaultProps.width`. 161 | 162 | ## 0.11.0-alpha.3 163 | 164 | - Implement `speedMultipler` prop to `BarLoader`. This is done to test the API for a single loader before adding to the rest. 165 | 166 | ## 0.11.0-alpha.2 167 | 168 | - Update readme usage section to use `@emotion` for `.babelrc` plugins 169 | 170 | ## 0.11.0-alpha.1 171 | 172 | - updated emotion to v11. [PR #329](https://github.com/davidhu2000/react-spinners/pull/329) 173 | 174 | ## 0.10.6 175 | 176 | - **bugfix**: Fixed MoonLoader display issue [#342](https://github.com/davidhu2000/react-spinners/pull/342) 177 | 178 | ## 0.10.4 179 | 180 | - Add `.eslintrc.*` to `.npmignore` to reduce packge size. 181 | 182 | ## 0.10.3 183 | 184 | - **bugfix**: Reverted `type:module` change in `package.json` due to [issue #336](https://github.com/davidhu2000/react-spinners/issues/336). This is causing a `Must use import to load ES Module` error. 185 | 186 | ## 0.10.2 187 | 188 | **Note: this release has a critical issue and was deprecated. Please update to 0.10.3 or higher.** 189 | 190 | - **bugfix**: the tsconfig compiler option was not overriding properly, so the outputted files are es2015 (with import syntax) vs commonjs (with require syntax. This could cause similar issues like [#74](https://github.com/davidhu2000/react-spinners/issues/74). 191 | 192 | ## 0.10.1 193 | 194 | **Note: this release has a critical issue and was deprecated. Please update to 0.10.3 or higher.** 195 | 196 | - Update README using react hooks. Move react class example under a summary tag. 197 | 198 | ## 0.10.0 199 | 200 | **Note: this release has a critical issue and was deprecated. Please update to 0.10.3 or higher.** 201 | 202 | - update `div` to `span` to fix `
cannot appear as a descendant of

` per [#159](https://github.com/davidhu2000/react-spinners/issues/159). [PR #325](https://github.com/davidhu2000/react-spinners/pull/325) 203 | - Using [lodash-es](https://github.com/lodash/lodash/blob/4.17.20-es/package.json#L10-L14) as a reference, added `type: module` to `package.json` as an attempt to implement tree shaking via [PR #327](https://github.com/davidhu2000/react-spinners/pull/327). 204 | - replaced tslint with eslint, and npm with yarn. 205 | 206 | ## 0.10.0-beta.3 207 | 208 | - Update `.npmignore` to ignore the `.cjs` files so they are not included in the published build. 209 | - Add `.yarn` and `.yarnrc` to `.npmignore`. 210 | 211 | Old: 212 | 213 | ``` 214 | npm notice version: 0.10.0-beta.2 215 | npm notice package size: 1.2 MB 216 | npm notice unpacked size: 5.3 MB 217 | npm notice total files: 69 218 | ``` 219 | 220 | New: 221 | 222 | ``` 223 | npm notice version: 0.10.0-beta.3 224 | npm notice package size: 21.3 kB 225 | npm notice unpacked size: 167.1 kB 226 | npm notice total files: 65 227 | ``` 228 | 229 | ## 0.10.0-beta.2 230 | 231 | - Using [lodash-es](https://github.com/lodash/lodash/blob/4.17.20-es/package.json#L10-L14) as a reference, added `type: module` to `package.json` as an attempt to fix tree shaking via [PR #327](https://github.com/davidhu2000/react-spinners/pull/327). 232 | - Renamed relevant `.js` files to `.cjs` so they are treated as CommonJs. Otherwise we get errors like `ReferenceError: module is not defined` when running certain commands, like `yarn`. 233 | 234 | ## 0.10.0-beta.1 235 | 236 | - No changes here. Upgrading alpha to beta. 237 | 238 | ## 0.10.0-alpha.3 239 | 240 | - add `react ^17.0.0` and `react-dom ^17.0.0` into peerDependencies to fix [#321](https://github.com/davidhu2000/react-spinners/issues/321) 241 | - update `div` to `span` to fix `

cannot appear as a descendant of

` per [#159](https://github.com/davidhu2000/react-spinners/issues/159). [PR #325](https://github.com/davidhu2000/react-spinners/pull/325) 242 | - removed `Keyframes` typing to allow for inferring [PR #326](https://github.com/davidhu2000/react-spinners/pull/326) 243 | - another round of update for all devDependencies to the latest version except for `react-color`, `react`, `react-dom`, and `@motion/core`. These 4 packages have caused issues during the update and will save them for another time. 244 | 245 | ## 0.10.0-alpha.2 246 | 247 | - add `sideEffects` property to `package.json` to fix tree shaking 248 | 249 | ## 0.10.0-alpha.1 250 | 251 | - updated all dependencies to the latest version. 252 | - switched from using npm to yarn 253 | - deprecated ts-lint in favor of eslint 254 | - updated how loaders are exported to support tree shaking 255 | 256 | ## 0.9.0 257 | 258 | - Added a new loader: `PuffLoader`. Thanks to @dsaw via [PR #200](https://github.com/davidhu2000/react-spinners/pull/200) 259 | - Update docs site with new loader 260 | 261 | ## 0.8.3 262 | 263 | - **Security**: Bump acorn from 5.7.3 to 5.7.4 due to `Regular Expression Denial of Service`. Details [here](https://github.com/advisories/GHSA-6chw-6frg-f759) 264 | 265 | ## 0.8.2 266 | 267 | - Add `box-sizing: content-box;` to MoonLoader. See [PR](https://github.com/davidhu2000/react-spinners/pull/162) for more details. 268 | 269 | ## 0.8.1 270 | 271 | - clean up README example: removed unrecommended import, removed comment out size prop, and bolded text for size prop being string and number 272 | 273 | ## 0.8.0 274 | 275 | - Added a new loader: `ClockLoader` 276 | - No other functionality changes 277 | - Fix default value table in README to alphabetize correctly 278 | 279 | ## 0.7.2 280 | 281 | - update README demo site url 282 | 283 | ## 0.7.1 284 | 285 | - run `npm audit fix` to fix vulnerability in `serialslize-javascript` package 286 | - update README to showcase number and string input for size prop 287 | 288 | ## 0.7.0 289 | 290 | - **BREAKING CHANGE**: all unit props have been removed to simplify the component API. See change log for `0.7.0-alpha.1` for more details 291 | 292 | ## 0.7.0-beta.1 293 | 294 | - Update readme to include yarn installation 295 | 296 | ## 0.7.0-alpha.5 297 | 298 | - clean up readme. break up prop section with individual prop headers 299 | 300 | ## 0.7.0-alpha.4 301 | 302 | - update default value for `css` prop on README to be `""` instead of `{}` 303 | - add list of available color words that the `color` prop accepts 304 | - run prettier to format readme 305 | 306 | ## 0.7.0-alpha.3 307 | 308 | - **bugfix**: Fix [issue #140](https://github.com/davidhu2000/react-spinners/issues/140). The margin prop on `FadeLoader` does what we expect it to do, expand the spacing between the lines 309 | 310 | ## 0.7.0-alpha.2 311 | 312 | - **bugfix**: Fix [issue #139](https://github.com/davidhu2000/react-spinners/issues/139). The margin prop on `RotateLoader` does what we expect it to do, expand the spacing between the dots 313 | - updated webpack config to split up npm files to avoid brower having to reload them on each change 314 | 315 | ## 0.7.0-alpha.1 316 | 317 | - **BREAKING CHANGE**: all unit props are deprecated, including `sizeUnit`, `heightUnit`, `widthUnit`, and `radiusUnit`. The `size`, `height`, `width`, and `radius` props now accepts `number` and `string` 318 | - If value is number, default to `px` 319 | - If value is string with valid css unit, return the input value 320 | - If value is string with invalid css unit, output warning console log and default to `px` 321 | - `margin` prop now work the same way as other length props. Can accept `number` and `string` 322 | - `css` prop default is now `""`. No functionality change here 323 | 324 | ## 0.6.1 325 | 326 | - **bugfix**: Fix [issue 109](https://github.com/davidhu2000/react-spinners/issues/109) where `Math.random` is stubbed out in the `GridLoader` component instead in the tests, causing `Math.random` to not work properly if `GridLoader` is used 327 | 328 | ## 0.6.0 329 | 330 | - Offical release for the TypeScript rewrite! 331 | - Major changes: 332 | - Add support for types for individual loader imports 333 | - Add support for using basic color name as props instead of only color hashes 334 | - Reduced total package size from around 850kb to 135gb 335 | - Fix `main` key value in `package.json` to point to the correct `index.js` 336 | - Removed `prop-types` and `recompose` from dependencies 337 | - Added tests to get to 100% code coverage 338 | 339 | ## 0.6.0-beta.1 340 | 341 | - updated `devDependencies` to latest versions 342 | 343 | ## 0.6.0-alpha.10 344 | 345 | - Removed `recompose` from the list of dependencies. We currently wants the component to update for all prop changes, so the `onlyUpdateForKeys` was passed in all the props anyways, so it wasn't doing much 346 | 347 | ## 0.6.0-alpha.9 348 | 349 | - **bugfix**: Fix issue where `PacmanLoader` `top` css property does not respect the `sizeUnit` prop. It was hardcoded to be `px` instead of using `sizeUnit` prop 350 | - update javascript bundle files for demo site 351 | 352 | ## 0.6.0-alpha.8 353 | 354 | - updated rgba conversion function to handle basic colors. Now supports these basically colors 355 | - maroon, red, orange, yellow, olive, green, purple, fuchsia, lime, teal, aqua, blue, navy, black, gray, silver, white 356 | 357 | ## 0.6.0-alpha.7 358 | 359 | - update readme to include `radius` and `radiusUnit` prop description 360 | - update all the tests to use default variables 361 | - add the following to `.npmignore` to further reduce package size 362 | 363 | ``` 364 | tslint.json 365 | jest.config.js 366 | CODEOWNERS 367 | CODE_OF_CONDUCT.md 368 | CONTRIBUTING.md 369 | CHANGELOG.md 370 | ``` 371 | 372 | Old: 373 | 374 | ``` 375 | npm notice version: 0.6.0-alpha.6 376 | npm notice package size: 19.8 kB 377 | npm notice unpacked size: 138.5 kB 378 | ``` 379 | 380 | New: 381 | 382 | ``` 383 | npm notice version: 0.6.0-alpha.7 384 | npm notice package size: 16.7 kB 385 | npm notice unpacked size: 132.1 kB 386 | ``` 387 | 388 | ## 0.6.0-alpha.6 389 | 390 | - add `src` folder to `npmignore`. Previous version wasn't ruthless enough in saving data 391 | 392 | Old: 393 | 394 | ``` 395 | npm notice version: 0.6.0-alpha.5 396 | npm notice package size: 26.1 kB 397 | npm notice unpacked size: 191.2 kB 398 | ``` 399 | 400 | New: 401 | 402 | ``` 403 | npm notice version: 0.6.0-alpha.6 404 | npm notice package size: 19.8 kB 405 | npm notice unpacked size: 138.5 kB 406 | ``` 407 | 408 | ## 0.6.0-alpha.5 409 | 410 | - update `npmignore` to include `__tests__`, `.github`, `.circleci`, `coverage`. This helped to reduce package size. Help to save some data 411 | 412 | Old: 413 | 414 | ``` 415 | npm notice version: 0.6.0-alpha.4 416 | npm notice package size: 85.6 kB 417 | npm notice unpacked size: 850.4 kB 418 | ``` 419 | 420 | New: 421 | 422 | ``` 423 | npm notice version: 0.6.0-alpha.5 424 | npm notice package size: 26.1 kB 425 | npm notice unpacked size: 191.2 kB 426 | ``` 427 | 428 | ## 0.6.0-alpha.4 429 | 430 | - **bugfix**: update `package.json` `main` value from `dist/index.js` to `index.js` to fix codeSandbox import issue 431 | - **bugfix**: add missing `transform` key to the `25%` keyframe in RiseLoader. It was just `25% {translateY(-${riseAmount}px)}` before. Now it is corrected 432 | - add tests for all the loaders. Fixed up a few tests using default variables, namely the first 3 letters in the alphabet 433 | 434 | ## 0.6.0-alpha.3 435 | 436 | - fix missing `"` from `.babelrc` in readme per [PR #77](https://github.com/davidhu2000/react-spinners/pull/77) 437 | - add tests for `ClipLoader`, `DotLoader`, `FadeLoader`, `GridLoader`, `HashLoader`, and `MoonLoader` 438 | 439 | ## 0.6.0-alpha.2 440 | 441 | - **bugfix**: update `tsconfig.json` `module` property to `commonjs` instead of `esnext`. This caused import errors as seen in [issue 74](https://github.com/davidhu2000/react-spinners/issues/74) 442 | - added tests for `BarLoader`, `BeatLoader`, `BounceLoader`, `CircleLoader`, and `ClimbingBoxLoader` 443 | 444 | ## 0.6.0-alpha.1 445 | 446 | - This is a complete rewrite of the package. 100% of the code is now in TypeScript. This will show inidividual type definitions for each loader 447 | - `prop-types` has been removed as a dependency. This is now handled by typings 448 | - set up `ts-lint` and `prettier` to help ensure code consistency 449 | 450 | ## 0.5.13 451 | 452 | **Note: this release has a critical [issue](https://github.com/davidhu2000/react-spinners/issues/74) and was deprecated. Please use <= 0.5.12 or > 0.6.0.** 453 | 454 | - fix readme props table formatting. It got a little messy for some reason 455 | 456 | ## 0.5.12 457 | 458 | - fix version glitch. No code changes here 459 | 460 | ## 0.5.11 461 | 462 | - this version should be 0.5.10, but internet issues causesa weird version glitch. Update to 0.5.12 so everything matches 463 | 464 | ## 0.5.10 465 | 466 | - update readme to include explanation of css prop can be string as well as css function from @emotion/core 467 | 468 | ## 0.5.9 469 | 470 | - **bugfix**: Fix [issue 61](https://github.com/davidhu2000/react-spinners/issues/61) where css overrides are not applied properly. Updated how the override workings using [emotion composition](https://emotion.sh/docs/composition) 471 | 472 | ## 0.5.8 473 | 474 | - **bugfix**: Fix [issue 66](https://github.com/davidhu2000/react-spinners/issues/66) where destructuring import no longer works. Updated how components are exported, changed from `export default` to `export const` 475 | 476 | ## 0.5.7 477 | 478 | - update README.md `.babelrc` example to use `@babel/` syntax in accordance to latest babel packages 479 | 480 | ## 0.5.6 481 | 482 | - big update for outdated devDependencies. This version should not affect any existing functionalities. 483 | - removed eslint related packages. Will be moving to use `tslint` as part of the typescript conversion. 484 | - updated babel plus plugins/presets to latest versions 485 | - updated `index.js` import from `module.exports = {...}` to `export default {...}` 486 | - webpack changes 487 | - added development configuration for easier debugging 488 | - add `html-webpack-plugin` to inject the script tags to `index.html` 489 | 490 | ## 0.5.5 491 | 492 | - **bugfix**: update `CommonProps` interface `css` prop to used `PrecompiledCss` and `string`. Update PropTypes helper to be able to accept both `PrecompiledCss` and `string`. This is to fix the validation error for the `css` prop 493 | 494 | ## 0.5.4 495 | 496 | - refactored proptypes into helper functions. No functionality change here, just some cleanups 497 | 498 | ## 0.5.3 499 | 500 | - **bugfix**: update default value for `css` prop to `{}` instead of `""` to fix console error 501 | 502 | ## 0.5.2 503 | 504 | - **bugfix**: change `css` proptype to `PropTypes.shape({ ... })` instead of `PropTypes.string` to fix console error. 505 | - Fix a few console warnings on the demo site 506 | 507 | ## 0.5.1 508 | 509 | - Update demo page link to `https://www.react-spinners.com` 510 | 511 | ## 0.5.0 512 | 513 | - Update emotion package to emotion 10 514 | - **Breaking change**: replaced `className` prop with `css` prop to match Emotion 10 515 | 516 | ## 0.4.8 517 | 518 | - update `package.json` to include wider range of version for `recompose` 519 | 520 | ## 0.4.7 521 | 522 | - add `loaders` and `spinners` keyword to package.json 523 | 524 | ## 0.4.6 525 | 526 | - update how `onlyUpdateForKeys` is imported from `recompose`. Reduced import cost from `26kb` to `19kb` 527 | 528 | ## 0.4.5 529 | 530 | - update README `.babelrc` to use `env` preset 531 | 532 | ## 0.4.4 533 | 534 | - fix README example import to using correct loader 535 | - add default value for unit props to README 536 | 537 | ## 0.4.3 538 | 539 | - update readme to include unit props for each loader 540 | 541 | ## 0.4.2 542 | 543 | - fix single loader import 544 | - add `className` to `index.d.ts` 545 | - update readme to include single loader import 546 | 547 | ## 0.4.1 548 | 549 | - Remove second import method from readme. Add deprecation warning to 0.4.0 550 | 551 | ## 0.4.0 552 | 553 | **Note: this release has a critical issue and was deprecated. Please update to 0.4.1 or higher.** 554 | 555 | - Add `className` prop to loaders 556 | - Deprecated `loaderStyle` prop for loaders to follow Emotion module standard 557 | 558 | ## 0.3.3 559 | 560 | **Note: this release was deprecated through removing `loaderStyle` prop. Please update to 0.4.1 or higher.** 561 | 562 | - Add `loaderStyle` prop to loaders to allow more customized loader 563 | 564 | ## 0.3.2 565 | 566 | - **bugfix**: fixed rendering issue for FadeLoader, SyncLoader, RotateLoader, and MoonLoader 567 | 568 | ## 0.3.1 569 | 570 | - Moved `babel-plugin-emotion` to devDependencies and updates to 9.1.0 571 | 572 | ## 0.3.0 573 | 574 | - Added `unit` props to each loader to allow `%` units on css 575 | - **bugfix**: fixed string concatenation on some loaders that prevented the correct rendering 576 | 577 | ## 0.2.6 578 | 579 | - **bugfix**: add missing `px` for `border-radius` in `ScaleLoader` styling 580 | - add `minor` and `major` versioning scripts to `package.json` 581 | 582 | ## 0.2.5 583 | 584 | - add `ISSUE_TEMPLATE.md` and `PULL_REQUEST_TEMPLATE.MD` 585 | 586 | ## 0.2.4 587 | 588 | - removed codesponsers from readme 589 | 590 | ## 0.2.3 591 | 592 | - updated devDendencies to latest stable versions 593 | - removed unused npm scripts from `package.json` 594 | - minor linting fixes after update 595 | - add `^16.0.0` to `react` and `react-dom` peerDependencies 596 | 597 | ## 0.2.2 598 | 599 | - **bugfix**: change `borderRadius` to `border-radius` in `RingLoader` so the browser will recognize the css 600 | 601 | ## 0.2.1 602 | 603 | - **bugfix**: moved `prop-types` to from devDependencies to dependencies. This fixes the `Package not found` error for projects that do not include `prop-types` as a dependency 604 | 605 | ## 0.2.0 606 | 607 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 608 | 609 | - add TypeScript typings 610 | 611 | ## 0.1.9 612 | 613 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 614 | 615 | - **bugfix**: moved `emotion` from devDependency to dependency. This fixed the `Package not found` error 616 | 617 | ## 0.1.8 618 | 619 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 620 | 621 | - update `emotion` package version from `7.2.0` to `8.0.6` 622 | 623 | ## 0.1.7 624 | 625 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 626 | 627 | - update dependencies versions 628 | 629 | ## 0.1.6 630 | 631 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 632 | 633 | - fixed some typo in readme 634 | 635 | ## 0.1.5 636 | 637 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 638 | 639 | - updated readme 640 | 641 | ## 0.1.4 642 | 643 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 644 | 645 | - **bugfix**: fixed `PulseLoader` size default prop to be the correct type 646 | 647 | ## 0.1.3 648 | 649 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 650 | 651 | - **bugfix**: moved `recompose` from devDependency to dependency 652 | - update author field in `package.json` 653 | 654 | ## 0.1.2 655 | 656 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 657 | 658 | - update margin column in readme proptype table 659 | - update contributors list in `package.json` 660 | 661 | ## 0.1.1 662 | 663 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 664 | 665 | - update readme to include note about `react-emotion` plugin for babel 666 | - fixed circleci badge to go to circle ci instead of npm 667 | - removed flow from test script 668 | 669 | ## 0.1.0 670 | 671 | **Note: this release has a critical issue and was deprecated. Please update to 0.2.1 or higher.** 672 | 673 | - removed `domkit` as a dependency and replaced it with `emotion`. This package now officially supports `Server Side Rendering 674 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo unless a later match takes precedence. 3 | # These owners will be requested for 4 | # review when someone opens a pull request. 5 | * @davidhu2000 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at davidhu314@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Want to contribute? Awesome. We love contributors. 4 | 5 | ## How to Contribute 6 | 7 | Fork then clone the repo: 8 | 9 | git clone git@github.com:your-username/react-spinners.git 10 | 11 | Create a new branch: 12 | 13 | git checkout -b awesome-feature 14 | 15 | Install the necessary dependencies (you can use `npm` or `yarn`): 16 | 17 | npm install 18 | 19 | If you want to make changes to the demo page, you can edit the files in `examples` and `docs` folder. 20 | 21 | To see the changes to the loaders or the demo site, you can use `webpack` to update the bundle file. 22 | 23 | npm run watch 24 | 25 | And open `./docs/index.html` in your favorite browser. 26 | 27 | After all the changes are made, make sure nothing changed in the demo site by running 28 | 29 | npm run build:demo 30 | 31 | And commit the file changes in the docs folder. 32 | 33 | Then commit your changes: 34 | 35 | git add -A; 36 | git commit -m 'Awesome new feature'; 37 | 38 | Make sure to run the necessary tests and lints and fix any errors: 39 | 40 | npm run lint; 41 | npm run test:jest; 42 | 43 | Push up to Github: 44 | 45 | git push origin awesome-feature; 46 | 47 | [Create a Pull Request][pr], add appropriate label(s). 48 | 49 | [pr]: https://www.github.com/davidhu2000/react-spinners/compare/ 50 | 51 | _Congratulations!_ You are done. Just wait for us to review your code. 52 | 53 | ## Issues or Feature Requests 54 | 55 | Please click [here](https://github.com/davidhu2000/react-spinners/issues/new) to report an issue or request a new feature. 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 David Hu 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Spinners 2 | 3 | [![npm version](https://badge.fury.io/js/react-spinners.svg)][npm_url] 4 | [![downloads](https://img.shields.io/npm/dt/react-spinners.svg)][npm_url] 5 | [![license](https://img.shields.io/npm/l/react-spinners.svg)][npm_url] 6 | 7 | 8 | 9 | [![Coverage Status](https://coveralls.io/repos/github/davidhu2000/react-spinners/badge.svg?branch=main)](https://coveralls.io/github/davidhu2000/react-spinners?branch=master) 10 | ![Dependency Count](https://badgen.net/bundlephobia/dependency-count/react-spinners) 11 | ![Types Included](https://badgen.net/npm/types/react-spinners) 12 | ![Tree Shaking Supported](https://badgen.net/bundlephobia/tree-shaking/react-spinners) 13 | 14 | [npm_url]: https://www.npmjs.org/package/react-spinners 15 | 16 | A collection of loading spinners with React.js based on [Halogen](https://github.com/yuanyan/halogen). 17 | 18 | This package is bootstraped using [react-npm-boilerplate](https://github.com/juliancwirko/react-npm-boilerplate) 19 | 20 | ## Demo 21 | 22 | [Demo Page](https://www.davidhu.io/react-spinners) 23 | 24 | [Storybook](https://www.davidhu.io/react-spinners/storybook/) 25 | 26 | ## Installation 27 | 28 | With Yarn: 29 | 30 | ```bash 31 | yarn add react-spinners 32 | ``` 33 | 34 | With npm: 35 | 36 | ```bash 37 | npm install --save react-spinners 38 | ``` 39 | 40 | ## Usage 41 | 42 | Each loader has their own default properties. You can overwrite the defaults by passing props into the loaders. 43 | 44 | Each loader accepts a `loading` prop as a boolean. The loader will render `null` if `loading` is `false`. 45 | 46 | ### Example 47 | 48 | ```tsx 49 | import { useState, CSSProperties } from "react"; 50 | import { ClipLoader } from "react-spinners"; 51 | 52 | const override: CSSProperties = { 53 | display: "block", 54 | margin: "0 auto", 55 | borderColor: "red", 56 | }; 57 | 58 | function App() { 59 | let [loading, setLoading] = useState(true); 60 | let [color, setColor] = useState("#ffffff"); 61 | 62 | return ( 63 |

64 | 65 | setColor(input.target.value)} 68 | placeholder="Color of the loader" 69 | /> 70 | 71 | 79 |
80 | ); 81 | } 82 | 83 | export default App; 84 | ``` 85 | 86 |
Example using React Class 87 | 88 | ```tsx 89 | import React from "react"; 90 | import { ClipLoader } from "react-spinners"; 91 | 92 | const override: React.CSSProperties = { 93 | display: "block", 94 | margin: "0 auto", 95 | borderColor: "red", 96 | }; 97 | 98 | class AwesomeComponent extends React.Component { 99 | constructor(props) { 100 | super(props); 101 | this.state = { 102 | loading: true, 103 | }; 104 | } 105 | 106 | render() { 107 | return ( 108 |
109 | 118 |
119 | ); 120 | } 121 | } 122 | ``` 123 | 124 |
125 | 126 | ## Available Loaders, PropTypes, and Default Values 127 | 128 | Common default props for all loaders: 129 | 130 | ``` 131 | loading: true; 132 | color: "#000000"; 133 | cssOverride: {} 134 | speedMultiplier: 1; 135 | ``` 136 | 137 | All valid HTML props such as `aria-*` and `data-*` props are fully supported. 138 | 139 | ### `color` prop 140 | 141 | `color` prop accepts a color hash in the format of `#XXXXXX` or `#XXX`. It also accepts basic colors listed below: 142 | 143 | > maroon, red, orange, yellow, olive, green, purple, white, 144 | > fuchsia, lime, teal, aqua, blue, navy, black, gray, silver 145 | 146 | ### `cssOverride` prop 147 | 148 | The `cssOverride` prop is an object of camelCase styles used to create inline styles on the loaders. Any html css property is valid here. 149 | 150 | ### `size`, `height`, `width`, and `radius` props 151 | 152 | The input to these props can be _number_ or _string_. 153 | 154 | - If value is number, the loader will default to css unit `px`. 155 | - If value is string, the loader will verify the unit against valid css units. 156 | - If unit is valid, return the original value 157 | - If unit is invalid, output warning console log and default to `px`. 158 | 159 | The table below has the default values for each loader. 160 | 161 | | Loader | size | height | width | radius | margin | 162 | | ----------------: | :--: | :----: | :---: | :----: | :----: | 163 | | BarLoader | | `4` | `100` | | 164 | | BeatLoader | `15` | | | | `2` | 165 | | BounceLoader | `60` | | | | 166 | | CircleLoader | `50` | | | | 167 | | ClimbingBoxLoader | `15` | | | | 168 | | ClipLoader | `35` | | | | 169 | | ClockLoader | `50` | | | | 170 | | DotLoader | `60` | | | | `2` | 171 | | FadeLoader | | `15` | `5` | `2` | `2` | 172 | | GridLoader | `15` | | | | 173 | | HashLoader | `50` | | | | `2` | 174 | | MoonLoader | `60` | | | | `2` | 175 | | PacmanLoader | `25` | | | | `2` | 176 | | PropagateLoader | `15` | | | | 177 | | PuffLoader | `60` | | | | 178 | | PulseLoader | `15` | | | | `2` | 179 | | RingLoader | `60` | | | | `2` | 180 | | RiseLoader | `15` | | | | `2` | 181 | | RotateLoader | `15` | | | | `2` | 182 | | ScaleLoader | | `35` | `4` | `2` | `2` | 183 | | SyncLoader | `15` | | | | `2` | 184 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 2 | import unicorn from "eslint-plugin-unicorn"; 3 | import jestDom from "eslint-plugin-jest-dom"; 4 | import testingLibrary from "eslint-plugin-testing-library"; 5 | import tsParser from "@typescript-eslint/parser"; 6 | 7 | export default [ 8 | { ignores: [".yarn/releases/*"] }, 9 | { 10 | plugins: { 11 | "@typescript-eslint": typescriptEslint, 12 | unicorn, 13 | "jest-dom": jestDom, 14 | "testing-library": testingLibrary, 15 | }, 16 | languageOptions: { 17 | parser: tsParser, 18 | }, 19 | settings: { 20 | react: { 21 | version: "detect", 22 | }, 23 | }, 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /examples/components/Code.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const text = ["yarn add react-spinners", "npm install react-spinners --save"]; 4 | 5 | function Code() { 6 | const [index, setIndex] = React.useState(0); 7 | 8 | const updateIndex = () => { 9 | setIndex(index === 0 ? 1 : 0); 10 | }; 11 | 12 | return {text[index]}; 13 | } 14 | 15 | export default Code; 16 | -------------------------------------------------------------------------------- /examples/components/ColorPicker.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { HexAlphaColorPicker } from "react-colorful"; 3 | 4 | interface ColorPickerProps { 5 | togglePicker: () => void; 6 | color: string; 7 | updateColor: (color: string) => void; 8 | } 9 | 10 | function Picker({ togglePicker, color, updateColor }: ColorPickerProps) { 11 | return ( 12 |
13 | 14 |
25 |
26 | ); 27 | } 28 | 29 | export default Picker; 30 | -------------------------------------------------------------------------------- /examples/components/LoaderItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { LoaderHeightWidthRadiusProps, LoaderSizeMarginProps } from "../../src/helpers/props"; 3 | 4 | type TProps = LoaderSizeMarginProps & LoaderHeightWidthRadiusProps; 5 | 6 | interface ItemProps { 7 | color: string; 8 | name: string; 9 | Spinner: React.ComponentType; 10 | } 11 | 12 | function LoaderItem({ color, Spinner, name }: ItemProps) { 13 | return ( 14 |
15 | 16 | {name} 17 | 18 | 19 |
20 | ); 21 | } 22 | 23 | export default LoaderItem; 24 | -------------------------------------------------------------------------------- /examples/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Code } from "./Code"; 2 | export { default as ColorPicker } from "./ColorPicker"; 3 | export { default as LoaderItem } from "./LoaderItem"; 4 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | React Spinners 20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 49 | 50 |
51 |
52 | yarn add react-spinners 53 |
54 | 55 |
56 |
57 |
58 | Loading 59 |
60 |
61 | Loading 62 |
63 |
64 | Loading 65 |
66 |
67 | Loading 68 |
69 |
70 | Loading 71 |
72 |
73 | Loading 74 |
75 |
76 |
77 | 78 |
79 | 80 | 85 | 86 | 87 | Fork me on GitHub 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /examples/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | 4 | import { Code, ColorPicker, LoaderItem } from "./components"; 5 | import * as Spinners from "../src"; 6 | import "./styles.css"; 7 | 8 | function SpinnerExamples() { 9 | const [color, setColor] = React.useState("#36D7B7"); 10 | const [showPicker, setShowPicker] = React.useState(false); 11 | 12 | React.useEffect(() => { 13 | document.addEventListener("scroll", () => { 14 | const picker = document.getElementsByClassName("color-picker")[0] as HTMLElement; 15 | const top = 370 - window.scrollY * 2; 16 | 17 | picker.style.top = `${top > 80 ? top : 70}px`; 18 | }); 19 | }, []); 20 | 21 | function updateColor(color: string) { 22 | setColor(color); 23 | const header = document.getElementById("header") as HTMLElement; 24 | header.style.cssText = ` 25 | background: -webkit-gradient(linear, left top, right top, from(${color}), to(#2b303b)); 26 | background: -webkit-linear-gradient(left, ${color}, #2b303b); 27 | background: -o-linear-gradient(left, ${color}, #2b303b); 28 | background: linear-gradient(90deg, ${color}, #2b303b); 29 | `; 30 | } 31 | 32 | function handleShowPicker() { 33 | setShowPicker(true); 34 | } 35 | function handleHidePicker() { 36 | setShowPicker(false); 37 | } 38 | 39 | return ( 40 |
41 |
42 | {showPicker ? ( 43 | 44 | ) : ( 45 | 46 | )} 47 |
48 | {Object.entries(Spinners).map(([name, loader]) => ( 49 | } 54 | /> 55 | ))} 56 |
57 | ); 58 | } 59 | 60 | document.addEventListener("DOMContentLoaded", () => { 61 | const root = document.getElementById("root"); 62 | 63 | if (root) { 64 | const reactRoot = createRoot(root); 65 | reactRoot.render(); 66 | } 67 | 68 | const code = document.getElementById("code"); 69 | 70 | if (code) { 71 | const reactCode = createRoot(code); 72 | reactCode.render(); 73 | } 74 | }); 75 | 76 | const header = document.getElementById("header") as HTMLElement; 77 | const main = document.getElementById("main") as HTMLElement; 78 | document.addEventListener("scroll", () => { 79 | const height = 360 - window.scrollY; 80 | if (height > 200) { 81 | header.style.height = `${height}px`; 82 | header.classList.remove("navbar"); 83 | header.style.boxShadow = ""; 84 | main.style.marginTop = "0"; 85 | } else { 86 | header.classList.add("navbar"); 87 | header.style.height = ""; 88 | header.style.boxShadow = 89 | "0 3px 3px 0 rgba(0,0,0,0.14), 0 1px 7px 0 rgba(0,0,0,0.12), 0 3px 1px -1px rgba(0,0,0,0.2)"; 90 | main.style.marginTop = "250px"; 91 | } 92 | }); 93 | -------------------------------------------------------------------------------- /examples/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #e2e2e2; 3 | font-family: "Roboto", sans-serif; 4 | margin: 0; 5 | overflow-x: hidden; 6 | } 7 | 8 | /* Header styles */ 9 | header { 10 | width: 100%; 11 | height: 360px; 12 | background: #2b303b; 13 | background: -o-linear-gradient(left, #36d7b7, #2b303b); 14 | background: -webkit-gradient(linear, left top, right top, from(#36d7b7), to(#2b303b)); 15 | background: linear-gradient(90deg, #36d7b7, #2b303b); 16 | color: #fff; 17 | display: -webkit-box; 18 | display: -ms-flexbox; 19 | display: flex; 20 | -webkit-box-align: center; 21 | -ms-flex-align: center; 22 | align-items: center; 23 | justify-content: center; 24 | flex-direction: column; 25 | z-index: 1000; 26 | } 27 | 28 | header.navbar { 29 | justify-content: space-around; 30 | } 31 | 32 | header a { 33 | color: white; 34 | cursor: pointer; 35 | } 36 | 37 | header a:hover { 38 | color: #e5e5e5; 39 | } 40 | 41 | .navbar { 42 | height: 60px !important; 43 | position: fixed; 44 | top: 0; 45 | } 46 | 47 | .navbar h1 { 48 | font-size: 20px; 49 | } 50 | 51 | .navbar h2 { 52 | font-size: 16px; 53 | } 54 | 55 | .logo { 56 | display: block; 57 | text-align: center; 58 | margin: 0px auto; 59 | font-size: 40px; 60 | letter-spacing: 5px; 61 | font-weight: 300; 62 | text-transform: uppercase; 63 | position: -webkit-sticky; 64 | position: sticky; 65 | top: 10px; 66 | } 67 | 68 | /* install styles */ 69 | .installation { 70 | margin: 50px auto; 71 | color: #fff; 72 | display: -webkit-box; 73 | display: -ms-flexbox; 74 | display: flex; 75 | -webkit-box-align: center; 76 | -ms-flex-align: center; 77 | align-items: center; 78 | cursor: pointer; 79 | } 80 | 81 | code { 82 | background: #2b303b; 83 | padding: 16px 32px; 84 | font-size: 20px; 85 | margin: 0 auto; 86 | font-family: 87 | Courier New, 88 | Courier, 89 | monospace; 90 | opacity: 0.9; 91 | } 92 | 93 | code:hover { 94 | -webkit-box-shadow: 95 | 0 3px 3px 0 rgba(0, 0, 0, 0.14), 96 | 0 1px 7px 0 rgba(0, 0, 0, 0.12), 97 | 0 3px 1px -1px rgba(0, 0, 0, 0.2); 98 | box-shadow: 99 | 0 3px 3px 0 rgba(0, 0, 0, 0.14), 100 | 0 1px 7px 0 rgba(0, 0, 0, 0.12), 101 | 0 3px 1px -1px rgba(0, 0, 0, 0.2); 102 | opacity: 1; 103 | } 104 | 105 | /* Spinner Container Styles */ 106 | .spinner-container { 107 | display: -webkit-box; 108 | display: -ms-flexbox; 109 | display: flex; 110 | -webkit-box-orient: horizontal; 111 | -webkit-box-direction: normal; 112 | -ms-flex-direction: row; 113 | flex-direction: row; 114 | -ms-flex-wrap: wrap; 115 | flex-wrap: wrap; 116 | -ms-flex-pack: distribute; 117 | justify-content: space-around; 118 | -webkit-box-align: center; 119 | -ms-flex-align: center; 120 | align-items: center; 121 | min-width: 300px; 122 | width: 80%; 123 | margin: 0 auto; 124 | background: #fff; 125 | } 126 | 127 | .spinner-item { 128 | -webkit-box-flex: 0; 129 | -ms-flex: 0 0 auto; 130 | flex: 0 0 auto; 131 | display: -webkit-box; 132 | display: -ms-flexbox; 133 | display: flex; 134 | -webkit-box-orient: vertical; 135 | -webkit-box-direction: normal; 136 | -ms-flex-direction: column; 137 | flex-direction: column; 138 | -webkit-box-align: center; 139 | -ms-flex-align: center; 140 | align-items: center; 141 | -webkit-box-pack: center; 142 | -ms-flex-pack: center; 143 | justify-content: center; 144 | border: 0.5px solid #e2e2e2; 145 | -webkit-box-shadow: 1px 1px 1px #e2e2e2; 146 | box-shadow: 1px 1px 1px #e2e2e2; 147 | width: 250px; 148 | height: 250px; 149 | margin: 30px; 150 | font-size: 18px; 151 | letter-spacing: 1px; 152 | position: relative; 153 | } 154 | 155 | .spinner-title { 156 | position: absolute; 157 | top: 20px; 158 | text-decoration: none; 159 | color: black; 160 | } 161 | 162 | .spinner-title:hover { 163 | text-decoration: underline; 164 | } 165 | 166 | /* color picker styles */ 167 | .color-picker { 168 | top: 370px; 169 | left: 20px; 170 | position: fixed; 171 | z-index: 999; 172 | } 173 | 174 | .color-picker button { 175 | height: 36px; 176 | font-size: 14px; 177 | line-height: 36px; 178 | background: #2b303b; 179 | color: #fff; 180 | border: none; 181 | opacity: 0.9; 182 | font-weight: 300; 183 | text-transform: uppercase; 184 | cursor: pointer; 185 | border-radius: 2px; 186 | letter-spacing: 0.5px; 187 | padding: 0 28px; 188 | } 189 | 190 | .color-picker button:hover { 191 | -webkit-box-shadow: 192 | 0 3px 3px 0 rgba(0, 0, 0, 0.14), 193 | 0 1px 7px 0 rgba(0, 0, 0, 0.12), 194 | 0 3px 1px -1px rgba(0, 0, 0, 0.2); 195 | box-shadow: 196 | 0 3px 3px 0 rgba(0, 0, 0, 0.14), 197 | 0 1px 7px 0 rgba(0, 0, 0, 0.12), 198 | 0 3px 1px -1px rgba(0, 0, 0, 0.2); 199 | opacity: 1; 200 | } 201 | 202 | /* Footer styles */ 203 | footer { 204 | padding: 30px; 205 | margin: 15px 0 0 0; 206 | background: #2b303b; 207 | text-align: center; 208 | display: flex; 209 | gap: 28px; 210 | justify-content: center; 211 | } 212 | 213 | @media (max-width: 640px) { 214 | footer { 215 | flex-direction: column; 216 | align-items: center; 217 | } 218 | footer a { 219 | margin-left: 0 !important; 220 | } 221 | } 222 | 223 | footer a { 224 | text-decoration: none; 225 | color: #ccc; 226 | } 227 | 228 | footer a:hover { 229 | color: #fff; 230 | } 231 | 232 | /* fork me on github ribbne */ 233 | #forkongithub a { 234 | background: #228b22; 235 | color: #fff; 236 | text-decoration: none; 237 | font-family: arial, sans-serif; 238 | text-align: center; 239 | font-weight: bold; 240 | padding: 5px 40px; 241 | font-size: 1rem; 242 | line-height: 2rem; 243 | position: relative; 244 | } 245 | #forkongithub a:hover { 246 | background: #008000; 247 | color: #fff; 248 | } 249 | #forkongithub a::before, 250 | #forkongithub a::after { 251 | content: ""; 252 | width: 100%; 253 | display: block; 254 | position: absolute; 255 | top: 1px; 256 | left: 0; 257 | height: 1px; 258 | background: #fff; 259 | } 260 | #forkongithub a::after { 261 | bottom: 1px; 262 | top: auto; 263 | } 264 | @media screen and (min-width: 600px) { 265 | #forkongithub { 266 | top: 0; 267 | right: 0; 268 | width: 200px; 269 | overflow: hidden; 270 | height: 200px; 271 | z-index: 9999; 272 | } 273 | #forkongithub a { 274 | width: 200px; 275 | position: absolute; 276 | top: 60px; 277 | right: -60px; 278 | transform: rotate(45deg); 279 | -webkit-transform: rotate(45deg); 280 | -ms-transform: rotate(45deg); 281 | -moz-transform: rotate(45deg); 282 | -o-transform: rotate(45deg); 283 | box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.8); 284 | } 285 | } 286 | 287 | @media screen and (max-width: 599px) { 288 | #forkongithub { 289 | display: none; 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | collectCoverage: true, 4 | coverageDirectory: "./coverage", 5 | moduleDirectories: ["node_modules"], 6 | transform: { 7 | ".(ts|tsx|js|jsx)": "ts-jest", 8 | }, 9 | roots: [""], 10 | moduleFileExtensions: ["ts", "tsx", "js", "jsx"], 11 | testEnvironment: "jsdom", 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-spinners", 3 | "version": "0.17.0", 4 | "description": "A collection of react loading spinners", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/davidhu2000/react-spinners.git" 8 | }, 9 | "author": "David Hu (https://www.davidhu.io)", 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/davidhu2000/react-spinners/issues" 13 | }, 14 | "homepage": "https://www.davidhu.io/react-spinners/", 15 | "contributors": [ 16 | "Cisco Guillaume (https://github.com/GuillaumeCisco)" 17 | ], 18 | "keywords": [ 19 | "react-spinners", 20 | "react-spinner", 21 | "react", 22 | "reactjs", 23 | "loader", 24 | "loaders", 25 | "loading", 26 | "spinner", 27 | "spinners", 28 | "halogen", 29 | "progress", 30 | "activity" 31 | ], 32 | "main": "index.js", 33 | "module": "esm/index.js", 34 | "scripts": { 35 | "prepare": "$npm_execpath run clean && $npm_execpath run build && $npm_execpath run build:esm", 36 | "build": "tsc --project tsconfig.cjs.json", 37 | "build:esm": "tsc --project tsconfig.esm.json --outDir esm", 38 | "build:demo": "$npm_execpath run vite build", 39 | "dev": "vite dev", 40 | "patch": "npm version patch && npm publish && npm run clean", 41 | "minor": "npm version minor && npm publish && npm run clean", 42 | "major": "npm version major && npm publish && npm run clean", 43 | "clean": "rm -rf helpers/; rm -f *Loader.js; rm -f *Loader.d.ts; rm -f index.js; rm -f index.d.ts; rm -rf esm", 44 | "lint": "eslint", 45 | "test": "jest", 46 | "coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls", 47 | "remove:tag": "npm dist-tag rm react-spinners next", 48 | "publish:next": "npm publish --tag next && npm run clean", 49 | "storybook": "storybook dev --docs -p 6006", 50 | "build-storybook": "storybook build --docs -o ./dist/storybook" 51 | }, 52 | "devDependencies": { 53 | "@storybook/addon-actions": "^8.6.12", 54 | "@storybook/addon-docs": "^8.6.12", 55 | "@storybook/addon-essentials": "^8.6.12", 56 | "@storybook/addon-interactions": "^8.6.12", 57 | "@storybook/addon-links": "^8.6.12", 58 | "@storybook/addon-webpack5-compiler-swc": "^3.0.0", 59 | "@storybook/addons": "7.6.20", 60 | "@storybook/react": "^8.6.12", 61 | "@storybook/react-vite": "^8.6.12", 62 | "@testing-library/jest-dom": "^6.6.3", 63 | "@testing-library/react": "^16.3.0", 64 | "@types/jest": "^29.5.14", 65 | "@types/react": "^19.1.2", 66 | "@types/react-click-outside": "^3.0.7", 67 | "@types/react-dom": "^19.1.2", 68 | "@typescript-eslint/eslint-plugin": "^8.30.0", 69 | "@typescript-eslint/parser": "^8.30.0", 70 | "@vitejs/plugin-react": "^4.3.4", 71 | "coveralls": "^3.1.1", 72 | "eslint": "^9.24.0", 73 | "eslint-config-prettier": "^10.1.2", 74 | "eslint-plugin-jest-dom": "^5.5.0", 75 | "eslint-plugin-react": "^7.37.5", 76 | "eslint-plugin-storybook": "^9.0.4", 77 | "eslint-plugin-testing-library": "^7.1.1", 78 | "eslint-plugin-unicorn": "^59.0.0", 79 | "jest": "^29.7.0", 80 | "jest-environment-jsdom": "^29.7.0", 81 | "prettier": "^3.5.3", 82 | "react": "^19.1.0", 83 | "react-colorful": "^5.6.1", 84 | "react-dom": "^19.1.0", 85 | "react-ga4": "^2.1.0", 86 | "storybook": "^8.6.12", 87 | "storybook-dark-mode": "^4.0.2", 88 | "ts-jest": "^29.3.2", 89 | "typescript": "^5.8.3", 90 | "vite": "^6.2.6" 91 | }, 92 | "peerDependencies": { 93 | "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", 94 | "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 95 | }, 96 | "sideEffects": false, 97 | "packageManager": "yarn@4.9.1" 98 | } 99 | -------------------------------------------------------------------------------- /scripts/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ruby scripts/stories.rb 4 | yarn run build-storybook --docs 5 | -------------------------------------------------------------------------------- /scripts/mod.rb: -------------------------------------------------------------------------------- 1 | filename = ARGV[0] + "Loader" 2 | 3 | path = "./src/#{filename}.tsx" 4 | file = File.open(path) 5 | 6 | `rm __tests__/#{filename}-tests.tsx` 7 | `rm __tests__/__snapshots__/#{filename}-tests.tsx.snap` 8 | 9 | def camelize(string) 10 | string.split("-").map do |part, index| 11 | return part if index == 0 12 | part.capitalize 13 | end.join("") 14 | end 15 | 16 | content = file.read 17 | 18 | puts "updating imports" 19 | content.sub!("/** @jsxImportSource @emotion/react */", "") 20 | content.sub!(/import .+ from "@emotion\/react";/, "") 21 | content.sub!("\"./interfaces\"", "\"./helpers/props\"; import { createAnimation } from \"./helpers/animation\"") 22 | 23 | puts "updating keyframes" 24 | content.gsub!(/keyframes`/, "createAnimation\(\"#{filename}\",`") 25 | content.sub!("`;", "`);") 26 | 27 | default = content.match(/public static defaultProps = (?.+)Defaults\((?.+)\);/) 28 | 29 | if default[:type] == "size" 30 | props = "size = #{default[:values]}," 31 | elsif default[:type] == "heightWidthRadius" 32 | values = default[:values].split(/\s*,\s*/) 33 | props = "height = #{values[0]}, width = #{values[1]}, radius = #{values[2]}," 34 | end 35 | 36 | content.sub!(/class Loader extends React.PureComponent> {/) do 37 | ["function #{filename}({", 38 | " loading = true,", 39 | ' color = "#000000",', 40 | " speedMultiplier = 1,", 41 | " css = {},", 42 | " #{props}", 43 | " ...additionalprops", 44 | "}: #{$1}) {"].join("\n") 45 | end 46 | 47 | content.sub!(/public static defaultProps = .+;/, "") 48 | 49 | content.gsub!(/const.+this.props;/, "") 50 | 51 | content.sub!(/public style = \((.*)\): SerializedStyles => {\n.+$/) do 52 | "const style = \(#{$1}\): React.CSSProperties => {" 53 | end 54 | 55 | content.gsub!("return css`", "return {") 56 | content.gsub!("`;", "};") 57 | 58 | content.gsub!(/public (\w+) = \(\): SerializedStyles => {\n\s+return {/) do 59 | "const #{$1}: React.CSSProperties = {" 60 | end 61 | content.gsub!(/};\n\s+};/, "};") 62 | 63 | content.gsub!(/(.+):\s*(.+);/) do 64 | "#{camelize($1)}: `#{$2}`," 65 | end 66 | 67 | content.gsub!("css=", "style=") 68 | content.gsub!("{this.", "{") 69 | 70 | content.gsub!("public render() {", "") 71 | content.gsub!(/}\n\s+}/, "}") 72 | 73 | content.gsub!("return loading ? (", "if (!loading) { return null; }\n\n return (") 74 | content.gsub!(") : `null`,", ");") 75 | 76 | content.gsub!("default Loader", "default #{filename}") 77 | 78 | # puts content 79 | File.write(path, content) 80 | -------------------------------------------------------------------------------- /scripts/stories.rb: -------------------------------------------------------------------------------- 1 | paths = Dir.glob("./src/*.tsx").filter do |path| 2 | !path.include?("stories") && !path.include?("test") 3 | end 4 | 5 | template = File.open("./scripts/stories.template").read 6 | 7 | def get_args(content) 8 | args = [] 9 | 10 | description = "Can be number or string. When number, unit is assumed as px. When string, a unit is expected to be passed in" 11 | 12 | ["size", "height", "width", "margin", "radius"].each do |arg| 13 | if content.include?("#{arg} =") 14 | args << " #{arg}: { description: \"#{description}\", control: { type: \"number\" } }," 15 | end 16 | end 17 | 18 | args.join("\n") 19 | end 20 | 21 | paths.sort.each do |path| 22 | puts "creating story for #{path}" 23 | file = File.open(path) 24 | loader = path.match(/\w+Loader/).to_s 25 | 26 | arg_types = get_args(file.read) 27 | 28 | story_path = path.sub(".tsx", ".stories.tsx").sub("./src", "./stories") 29 | 30 | story = template.gsub("LOADER_NAME", loader).gsub("ARG_TYPES", arg_types) 31 | 32 | File.write(story_path, story) 33 | end 34 | 35 | puts "done creating stories" 36 | -------------------------------------------------------------------------------- /scripts/stories.template: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | 3 | import LOADER_NAME from "../src/LOADER_NAME"; 4 | 5 | const meta: Meta = { 6 | component: LOADER_NAME, 7 | argTypes:{ 8 | ARG_TYPES 9 | } 10 | }; 11 | 12 | export default meta; 13 | 14 | type Story = StoryObj; 15 | 16 | export const Primary: Story = {}; 17 | 18 | -------------------------------------------------------------------------------- /src/BarLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { createAnimation } from "./helpers/animation"; 7 | import { LoaderHeightWidthProps } from "./helpers/props"; 8 | import { calculateRgba } from "./helpers/colors"; 9 | 10 | const long = createAnimation( 11 | "BarLoader", 12 | `0% {left: -35%;right: 100%} 60% {left: 100%;right: -90%} 100% {left: 100%;right: -90%}`, 13 | "long" 14 | ); 15 | 16 | const short = createAnimation( 17 | "BarLoader", 18 | `0% {left: -200%;right: 100%} 60% {left: 107%;right: -8%} 100% {left: 107%;right: -8%}`, 19 | "short" 20 | ); 21 | 22 | function BarLoader({ 23 | loading = true, 24 | color = "#000000", 25 | speedMultiplier = 1, 26 | cssOverride = {}, 27 | height = 4, 28 | width = 100, 29 | ...additionalprops 30 | }: LoaderHeightWidthProps) { 31 | const wrapper: React.CSSProperties = { 32 | display: "inherit", 33 | position: "relative", 34 | width: cssValue(width), 35 | height: cssValue(height), 36 | overflow: "hidden", 37 | backgroundColor: calculateRgba(color, 0.2), 38 | backgroundClip: "padding-box", 39 | ...cssOverride, 40 | }; 41 | 42 | const style = (i: number): React.CSSProperties => { 43 | return { 44 | position: "absolute", 45 | height: cssValue(height), 46 | overflow: "hidden", 47 | backgroundColor: color, 48 | backgroundClip: "padding-box", 49 | display: "block", 50 | borderRadius: 2, 51 | willChange: "left, right", 52 | animationFillMode: "forwards", 53 | animation: `${i === 1 ? long : short} ${2.1 / speedMultiplier}s ${i === 2 ? `${1.15 / speedMultiplier}s` : ""} ${ 54 | i === 1 ? "cubic-bezier(0.65, 0.815, 0.735, 0.395)" : "cubic-bezier(0.165, 0.84, 0.44, 1)" 55 | } infinite`, 56 | }; 57 | }; 58 | 59 | if (!loading) { 60 | return null; 61 | } 62 | 63 | return ( 64 | 65 | 66 | 67 | 68 | ); 69 | } 70 | 71 | export default BarLoader; 72 | -------------------------------------------------------------------------------- /src/BeatLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { createAnimation } from "./helpers/animation"; 7 | import { LoaderSizeMarginProps } from "./helpers/props"; 8 | 9 | const beat = createAnimation( 10 | "BeatLoader", 11 | "50% {transform: scale(0.75);opacity: 0.2} 100% {transform: scale(1);opacity: 1}", 12 | "beat" 13 | ); 14 | 15 | function BeatLoader({ 16 | loading = true, 17 | color = "#000000", 18 | speedMultiplier = 1, 19 | cssOverride = {}, 20 | size = 15, 21 | margin = 2, 22 | ...additionalprops 23 | }: LoaderSizeMarginProps) { 24 | const wrapper: React.CSSProperties = { 25 | display: "inherit", 26 | ...cssOverride, 27 | }; 28 | 29 | const style = (i: number): React.CSSProperties => { 30 | return { 31 | display: "inline-block", 32 | backgroundColor: color, 33 | width: cssValue(size), 34 | height: cssValue(size), 35 | margin: cssValue(margin), 36 | borderRadius: "100%", 37 | animation: `${beat} ${0.7 / speedMultiplier}s ${i % 2 ? "0s" : `${0.35 / speedMultiplier}s`} infinite linear`, 38 | animationFillMode: "both", 39 | }; 40 | }; 41 | 42 | if (!loading) { 43 | return null; 44 | } 45 | 46 | return ( 47 | 48 | 49 | 50 | 51 | 52 | ); 53 | } 54 | 55 | export default BeatLoader; 56 | -------------------------------------------------------------------------------- /src/BounceLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const bounce = createAnimation( 10 | "BounceLoader", 11 | "0% {transform: scale(0)} 50% {transform: scale(1.0)} 100% {transform: scale(0)}", 12 | "bounce" 13 | ); 14 | 15 | function BounceLoader({ 16 | loading = true, 17 | color = "#000000", 18 | speedMultiplier = 1, 19 | cssOverride = {}, 20 | size = 60, 21 | ...additionalprops 22 | }: LoaderSizeProps) { 23 | const style = (i: number): React.CSSProperties => { 24 | const animationTiming = i === 1 ? `${1 / speedMultiplier}s` : "0s"; 25 | return { 26 | position: "absolute", 27 | height: cssValue(size), 28 | width: cssValue(size), 29 | backgroundColor: color, 30 | borderRadius: "100%", 31 | opacity: 0.6, 32 | top: 0, 33 | left: 0, 34 | animationFillMode: "both", 35 | animation: `${bounce} ${2.1 / speedMultiplier}s ${animationTiming} infinite ease-in-out`, 36 | }; 37 | }; 38 | 39 | const wrapper: React.CSSProperties = { 40 | display: "inherit", 41 | position: "relative", 42 | width: cssValue(size), 43 | height: cssValue(size), 44 | ...cssOverride, 45 | }; 46 | 47 | if (!loading) { 48 | return null; 49 | } 50 | 51 | return ( 52 | 53 | 54 | 55 | 56 | ); 57 | } 58 | 59 | export default BounceLoader; 60 | -------------------------------------------------------------------------------- /src/CircleLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue, parseLengthAndUnit } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const circle = createAnimation( 10 | "CircleLoader", 11 | "0% {transform: rotate(0deg)} 50% {transform: rotate(180deg)} 100% {transform: rotate(360deg)}", 12 | "circle" 13 | ); 14 | 15 | function CircleLoader({ 16 | loading = true, 17 | color = "#000000", 18 | speedMultiplier = 1, 19 | cssOverride = {}, 20 | size = 50, 21 | ...additionalprops 22 | }: LoaderSizeProps) { 23 | const wrapper: React.CSSProperties = { 24 | display: "inherit", 25 | position: "relative", 26 | width: cssValue(size), 27 | height: cssValue(size), 28 | ...cssOverride, 29 | }; 30 | 31 | const style = (i: number): React.CSSProperties => { 32 | const { value, unit } = parseLengthAndUnit(size); 33 | 34 | return { 35 | position: "absolute", 36 | height: `${value * (1 - i / 10)}${unit}`, 37 | width: `${value * (1 - i / 10)}${unit}`, 38 | borderTop: `1px solid ${color}`, 39 | borderBottom: "none", 40 | borderLeft: `1px solid ${color}`, 41 | borderRight: "none", 42 | borderRadius: "100%", 43 | transition: "2s", 44 | top: `${i * 0.7 * 2.5}%`, 45 | left: `${i * 0.35 * 2.5}%`, 46 | animation: `${circle} ${1 / speedMultiplier}s ${(i * 0.2) / speedMultiplier}s infinite linear`, 47 | }; 48 | }; 49 | 50 | if (!loading) { 51 | return null; 52 | } 53 | 54 | return ( 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | 65 | export default CircleLoader; 66 | -------------------------------------------------------------------------------- /src/ClimbingBoxLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const climbingBox = createAnimation( 10 | "ClimbingBoxLoader", 11 | `0% {transform:translate(0, -1em) rotate(-45deg)} 12 | 5% {transform:translate(0, -1em) rotate(-50deg)} 13 | 20% {transform:translate(1em, -2em) rotate(47deg)} 14 | 25% {transform:translate(1em, -2em) rotate(45deg)} 15 | 30% {transform:translate(1em, -2em) rotate(40deg)} 16 | 45% {transform:translate(2em, -3em) rotate(137deg)} 17 | 50% {transform:translate(2em, -3em) rotate(135deg)} 18 | 55% {transform:translate(2em, -3em) rotate(130deg)} 19 | 70% {transform:translate(3em, -4em) rotate(217deg)} 20 | 75% {transform:translate(3em, -4em) rotate(220deg)} 21 | 100% {transform:translate(0, -1em) rotate(-225deg)}`, 22 | "climbingBox" 23 | ); 24 | 25 | function ClimbingBoxLoader({ 26 | loading = true, 27 | color = "#000000", 28 | speedMultiplier = 1, 29 | cssOverride = {}, 30 | size = 15, 31 | ...additionalprops 32 | }: LoaderSizeProps) { 33 | const container: React.CSSProperties = { 34 | display: "inherit", 35 | position: "relative", 36 | width: "7.1em", 37 | height: "7.1em", 38 | ...cssOverride, 39 | }; 40 | 41 | const wrapper: React.CSSProperties = { 42 | position: "absolute", 43 | top: "50%", 44 | left: "50%", 45 | marginTop: "-2.7em", 46 | marginLeft: "-2.7em", 47 | width: "5.4em", 48 | height: "5.4em", 49 | fontSize: cssValue(size), 50 | }; 51 | 52 | const style: React.CSSProperties = { 53 | position: "absolute", 54 | left: "0", 55 | bottom: "-0.1em", 56 | height: "1em", 57 | width: "1em", 58 | backgroundColor: "transparent", 59 | borderRadius: "15%", 60 | border: `0.25em solid ${color}`, 61 | transform: "translate(0, -1em) rotate(-45deg)", 62 | animationFillMode: "both", 63 | animation: `${climbingBox} ${2.5 / speedMultiplier}s infinite cubic-bezier(0.79, 0, 0.47, 0.97)`, 64 | }; 65 | 66 | const hill: React.CSSProperties = { 67 | position: "absolute", 68 | width: "7.1em", 69 | height: "7.1em", 70 | top: "1.7em", 71 | left: "1.7em", 72 | borderLeft: `0.25em solid ${color}`, 73 | transform: "rotate(45deg)", 74 | }; 75 | 76 | if (!loading) { 77 | return null; 78 | } 79 | 80 | return ( 81 | 82 | 83 | 84 | 85 | 86 | 87 | ); 88 | } 89 | 90 | export default ClimbingBoxLoader; 91 | -------------------------------------------------------------------------------- /src/ClipLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const clip = createAnimation( 10 | "ClipLoader", 11 | "0% {transform: rotate(0deg) scale(1)} 50% {transform: rotate(180deg) scale(0.8)} 100% {transform: rotate(360deg) scale(1)}", 12 | "clip" 13 | ); 14 | 15 | function ClipLoader({ 16 | loading = true, 17 | color = "#000000", 18 | speedMultiplier = 1, 19 | cssOverride = {}, 20 | size = 35, 21 | ...additionalprops 22 | }: LoaderSizeProps) { 23 | const style: React.CSSProperties = { 24 | background: "transparent !important", 25 | width: cssValue(size), 26 | height: cssValue(size), 27 | borderRadius: "100%", 28 | border: "2px solid", 29 | borderTopColor: color, 30 | borderBottomColor: "transparent", 31 | borderLeftColor: color, 32 | borderRightColor: color, 33 | display: "inline-block", 34 | animation: `${clip} ${0.75 / speedMultiplier}s 0s infinite linear`, 35 | animationFillMode: "both", 36 | ...cssOverride, 37 | }; 38 | 39 | if (!loading) { 40 | return null; 41 | } 42 | 43 | return ; 44 | } 45 | 46 | export default ClipLoader; 47 | -------------------------------------------------------------------------------- /src/ClockLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { parseLengthAndUnit } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const rotate = createAnimation("ClockLoader", "100% { transform: rotate(360deg) }", "rotate"); 10 | 11 | function ClockLoader({ 12 | loading = true, 13 | color = "#000000", 14 | speedMultiplier = 1, 15 | cssOverride = {}, 16 | size = 50, 17 | ...additionalprops 18 | }: LoaderSizeProps) { 19 | const { value, unit } = parseLengthAndUnit(size); 20 | 21 | const wrapper: React.CSSProperties = { 22 | display: "inherit", 23 | position: "relative", 24 | width: `${value}${unit}`, 25 | height: `${value}${unit}`, 26 | backgroundColor: "transparent", 27 | boxShadow: `inset 0px 0px 0px 2px ${color}`, 28 | borderRadius: "50%", 29 | ...cssOverride, 30 | }; 31 | 32 | const minute: React.CSSProperties = { 33 | position: "absolute", 34 | backgroundColor: color, 35 | width: `${value / 3}px`, 36 | height: "2px", 37 | top: `${value / 2 - 1}px`, 38 | left: `${value / 2 - 1}px`, 39 | transformOrigin: "1px 1px", 40 | animation: `${rotate} ${8 / speedMultiplier}s linear infinite`, 41 | }; 42 | 43 | const hour: React.CSSProperties = { 44 | position: "absolute", 45 | backgroundColor: color, 46 | width: `${value / 2.4}px`, 47 | height: "2px", 48 | top: `${value / 2 - 1}px`, 49 | left: `${value / 2 - 1}px`, 50 | transformOrigin: "1px 1px", 51 | animation: `${rotate} ${2 / speedMultiplier}s linear infinite`, 52 | }; 53 | 54 | if (!loading) { 55 | return null; 56 | } 57 | 58 | return ( 59 | 60 | 61 | 62 | 63 | ); 64 | } 65 | 66 | export default ClockLoader; 67 | -------------------------------------------------------------------------------- /src/DotLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { parseLengthAndUnit, cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const rotate = createAnimation("DotLoader", "100% {transform: rotate(360deg)}", "rotate"); 10 | 11 | const bounce = createAnimation( 12 | "DotLoader", 13 | "0%, 100% {transform: scale(0)} 50% {transform: scale(1.0)}", 14 | "bounce" 15 | ); 16 | 17 | function DotLoader({ 18 | loading = true, 19 | color = "#000000", 20 | speedMultiplier = 1, 21 | cssOverride = {}, 22 | size = 60, 23 | ...additionalprops 24 | }: LoaderSizeProps) { 25 | const wrapper: React.CSSProperties = { 26 | display: "inherit", 27 | position: "relative", 28 | width: cssValue(size), 29 | height: cssValue(size), 30 | animationFillMode: "forwards", 31 | animation: `${rotate} ${2 / speedMultiplier}s 0s infinite linear`, 32 | ...cssOverride, 33 | }; 34 | 35 | const style = (i: number): React.CSSProperties => { 36 | const { value, unit } = parseLengthAndUnit(size); 37 | 38 | return { 39 | position: "absolute", 40 | top: i % 2 ? "0" : "auto", 41 | bottom: i % 2 ? "auto" : "0", 42 | height: `${value / 2}${unit}`, 43 | width: `${value / 2}${unit}`, 44 | backgroundColor: color, 45 | borderRadius: "100%", 46 | animationFillMode: "forwards", 47 | animation: `${bounce} ${2 / speedMultiplier}s ${i === 2 ? "1s" : "0s"} infinite linear`, 48 | }; 49 | }; 50 | 51 | if (!loading) { 52 | return null; 53 | } 54 | 55 | return ( 56 | 57 | 58 | 59 | 60 | ); 61 | } 62 | 63 | export default DotLoader; 64 | -------------------------------------------------------------------------------- /src/FadeLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue, parseLengthAndUnit } from "./helpers/unitConverter"; 6 | import { LoaderHeightWidthRadiusProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const fade = createAnimation("FadeLoader", "50% {opacity: 0.3} 100% {opacity: 1}", "fade"); 10 | 11 | function FadeLoader({ 12 | loading = true, 13 | color = "#000000", 14 | speedMultiplier = 1, 15 | cssOverride = {}, 16 | height = 15, 17 | width = 5, 18 | radius = 2, 19 | margin = 2, 20 | ...additionalprops 21 | }: LoaderHeightWidthRadiusProps) { 22 | const { value } = parseLengthAndUnit(margin); 23 | const radiusValue = value + 18; 24 | const quarter = radiusValue / 2 + radiusValue / 5.5; 25 | 26 | const wrapper: React.CSSProperties = { 27 | display: "inherit", 28 | position: "relative", 29 | fontSize: "0", 30 | top: radiusValue, 31 | left: radiusValue, 32 | width: `${radiusValue * 3}px`, 33 | height: `${radiusValue * 3}px`, 34 | ...cssOverride, 35 | }; 36 | 37 | const style = (i: number): React.CSSProperties => { 38 | return { 39 | position: "absolute", 40 | width: cssValue(width), 41 | height: cssValue(height), 42 | margin: cssValue(margin), 43 | backgroundColor: color, 44 | borderRadius: cssValue(radius), 45 | transition: "2s", 46 | animationFillMode: "both", 47 | animation: `${fade} ${1.2 / speedMultiplier}s ${i * 0.12}s infinite ease-in-out`, 48 | }; 49 | }; 50 | 51 | const a: React.CSSProperties = { 52 | ...style(1), 53 | top: `${radiusValue}px`, 54 | left: "0", 55 | }; 56 | const b: React.CSSProperties = { 57 | ...style(2), 58 | top: `${quarter}px`, 59 | left: `${quarter}px`, 60 | transform: "rotate(-45deg)", 61 | }; 62 | const c: React.CSSProperties = { 63 | ...style(3), 64 | top: "0", 65 | left: `${radiusValue}px`, 66 | transform: "rotate(90deg)", 67 | }; 68 | const d: React.CSSProperties = { 69 | ...style(4), 70 | top: `${-1 * quarter}px`, 71 | left: `${quarter}px`, 72 | transform: "rotate(45deg)", 73 | }; 74 | const e: React.CSSProperties = { 75 | ...style(5), 76 | top: `${-1 * radiusValue}px`, 77 | left: "0", 78 | }; 79 | const f: React.CSSProperties = { 80 | ...style(6), 81 | top: `${-1 * quarter}px`, 82 | left: `${-1 * quarter}px`, 83 | transform: "rotate(-45deg)", 84 | }; 85 | const g: React.CSSProperties = { 86 | ...style(7), 87 | top: "0", 88 | left: `${-1 * radiusValue}px`, 89 | transform: "rotate(90deg)", 90 | }; 91 | const h: React.CSSProperties = { 92 | ...style(8), 93 | top: `${quarter}px`, 94 | left: `${-1 * quarter}px`, 95 | transform: "rotate(45deg)", 96 | }; 97 | 98 | if (!loading) { 99 | return null; 100 | } 101 | 102 | return ( 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | ); 114 | } 115 | 116 | export default FadeLoader; 117 | -------------------------------------------------------------------------------- /src/GridLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue, parseLengthAndUnit } from "./helpers/unitConverter"; 6 | import { LoaderSizeMarginProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const grid = createAnimation( 10 | "GridLoader", 11 | "0% {transform: scale(1)} 50% {transform: scale(0.5); opacity: 0.7} 100% {transform: scale(1); opacity: 1}", 12 | "grid" 13 | ); 14 | 15 | const random = (top: number): number => Math.random() * top; 16 | 17 | function GridLoader({ 18 | loading = true, 19 | color = "#000000", 20 | speedMultiplier = 1, 21 | cssOverride = {}, 22 | size = 15, 23 | margin = 2, 24 | ...additionalprops 25 | }: LoaderSizeMarginProps) { 26 | const sizeWithUnit = parseLengthAndUnit(size); 27 | const marginWithUnit = parseLengthAndUnit(margin); 28 | 29 | const width = 30 | parseFloat(sizeWithUnit.value.toString()) * 3 + parseFloat(marginWithUnit.value.toString()) * 6; 31 | 32 | const wrapper: React.CSSProperties = { 33 | width: `${width}${sizeWithUnit.unit}`, 34 | fontSize: 0, 35 | display: "inline-block", 36 | ...cssOverride, 37 | }; 38 | 39 | const style = (rand: number): React.CSSProperties => { 40 | return { 41 | display: "inline-block", 42 | backgroundColor: color, 43 | width: `${cssValue(size)}`, 44 | height: `${cssValue(size)}`, 45 | margin: cssValue(margin), 46 | borderRadius: "100%", 47 | animationFillMode: "both", 48 | animation: `${grid} ${(rand / 100 + 0.6) / speedMultiplier}s ${rand / 100 - 0.2}s infinite ease`, 49 | }; 50 | }; 51 | 52 | if (!loading) { 53 | return null; 54 | } 55 | 56 | return ( 57 | { 61 | if (node) { 62 | node.style.setProperty("width", `${width}${sizeWithUnit.unit}`, "important"); 63 | } 64 | }} 65 | > 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ); 77 | } 78 | 79 | export default GridLoader; 80 | -------------------------------------------------------------------------------- /src/HashLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { calculateRgba } from "./helpers/colors"; 6 | import { parseLengthAndUnit, cssValue } from "./helpers/unitConverter"; 7 | import { LoaderSizeProps } from "./helpers/props"; 8 | import { createAnimation } from "./helpers/animation"; 9 | 10 | function HashLoader({ 11 | loading = true, 12 | color = "#000000", 13 | speedMultiplier = 1, 14 | cssOverride = {}, 15 | size = 50, 16 | ...additionalprops 17 | }: LoaderSizeProps) { 18 | const { value, unit } = parseLengthAndUnit(size); 19 | 20 | const wrapper: React.CSSProperties = { 21 | display: "inherit", 22 | position: "relative", 23 | width: cssValue(size), 24 | height: cssValue(size), 25 | transform: "rotate(165deg)", 26 | ...cssOverride, 27 | }; 28 | 29 | const thickness = value / 5; 30 | 31 | const lat = (value - thickness) / 2; 32 | 33 | const offset = lat - thickness; 34 | 35 | const colorValue = calculateRgba(color, 0.75); 36 | 37 | const before = createAnimation( 38 | "HashLoader", 39 | `0% {width: ${thickness}px; box-shadow: ${lat}px ${-offset}px ${colorValue}, ${-lat}px ${offset}px ${colorValue}} 40 | 35% {width: ${cssValue(size)}; box-shadow: 0 ${-offset}px ${colorValue}, 0 ${offset}px ${colorValue}} 41 | 70% {width: ${thickness}px; box-shadow: ${-lat}px ${-offset}px ${colorValue}, ${lat}px ${offset}px ${colorValue}} 42 | 100% {box-shadow: ${lat}px ${-offset}px ${colorValue}, ${-lat}px ${offset}px ${colorValue}}`, 43 | "before" 44 | ); 45 | 46 | const after = createAnimation( 47 | "HashLoader", 48 | `0% {height: ${thickness}px; box-shadow: ${offset}px ${lat}px ${color}, ${-offset}px ${-lat}px ${color}} 49 | 35% {height: ${cssValue(size)}; box-shadow: ${offset}px 0 ${color}, ${-offset}px 0 ${color}} 50 | 70% {height: ${thickness}px; box-shadow: ${offset}px ${-lat}px ${color}, ${-offset}px ${lat}px ${color}} 51 | 100% {box-shadow: ${offset}px ${lat}px ${color}, ${-offset}px ${-lat}px ${color}}`, 52 | "after" 53 | ); 54 | 55 | const style = (i: number): React.CSSProperties => { 56 | return { 57 | position: "absolute", 58 | top: "50%", 59 | left: "50%", 60 | display: "block", 61 | width: `${value / 5}${unit}`, 62 | height: `${value / 5}${unit}`, 63 | borderRadius: `${value / 10}${unit}`, 64 | transform: "translate(-50%, -50%)", 65 | animationFillMode: "none", 66 | animation: `${i === 1 ? before : after} ${2 / speedMultiplier}s infinite`, 67 | }; 68 | }; 69 | 70 | if (!loading) { 71 | return null; 72 | } 73 | 74 | return ( 75 | 76 | 77 | 78 | 79 | ); 80 | } 81 | 82 | export default HashLoader; 83 | -------------------------------------------------------------------------------- /src/MoonLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { parseLengthAndUnit, cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const moon = createAnimation("MoonLoader", "100% {transform: rotate(360deg)}", "moon"); 10 | 11 | function MoonLoader({ 12 | loading = true, 13 | color = "#000000", 14 | speedMultiplier = 1, 15 | cssOverride = {}, 16 | size = 60, 17 | ...additionalprops 18 | }: LoaderSizeProps) { 19 | const { value, unit } = parseLengthAndUnit(size); 20 | const moonSize = Math.round(value / 7); 21 | 22 | const wrapper: React.CSSProperties = { 23 | display: "inherit", 24 | position: "relative", 25 | width: `${`${value + moonSize * 2}${unit}`}`, 26 | height: `${`${value + moonSize * 2}${unit}`}`, 27 | animation: `${moon} ${0.6 / speedMultiplier}s 0s infinite linear`, 28 | animationFillMode: "forwards", 29 | ...cssOverride, 30 | }; 31 | 32 | const ballStyle = (size: number): React.CSSProperties => { 33 | return { 34 | width: cssValue(size), 35 | height: cssValue(size), 36 | borderRadius: "100%", 37 | }; 38 | }; 39 | 40 | const ball: React.CSSProperties = { 41 | ...ballStyle(moonSize), 42 | backgroundColor: `${color}`, 43 | opacity: "0.8", 44 | position: "absolute", 45 | top: `${`${value / 2 - moonSize / 2}${unit}`}`, 46 | animation: `${moon} ${0.6 / speedMultiplier}s 0s infinite linear`, 47 | animationFillMode: "forwards", 48 | }; 49 | 50 | const circle: React.CSSProperties = { 51 | ...ballStyle(value), 52 | border: `${moonSize}px solid ${color}`, 53 | opacity: "0.1", 54 | boxSizing: "content-box", 55 | position: "absolute", 56 | }; 57 | 58 | if (!loading) { 59 | return null; 60 | } 61 | 62 | return ( 63 | 64 | 65 | 66 | 67 | ); 68 | } 69 | 70 | export default MoonLoader; 71 | -------------------------------------------------------------------------------- /src/PacmanLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { parseLengthAndUnit, cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeMarginProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const pacman = [ 10 | createAnimation( 11 | "PacmanLoader", 12 | "0% {transform: rotate(0deg)} 50% {transform: rotate(-44deg)}", 13 | "pacman-1" 14 | ), 15 | createAnimation( 16 | "PacmanLoader", 17 | "0% {transform: rotate(0deg)} 50% {transform: rotate(44deg)}", 18 | "pacman-2" 19 | ), 20 | ]; 21 | 22 | function PacmanLoader({ 23 | loading = true, 24 | color = "#000000", 25 | speedMultiplier = 1, 26 | cssOverride = {}, 27 | size = 25, 28 | margin = 2, 29 | ...additionalprops 30 | }: LoaderSizeMarginProps) { 31 | const { value, unit } = parseLengthAndUnit(size); 32 | 33 | const wrapper: React.CSSProperties = { 34 | display: "inherit", 35 | position: "relative", 36 | fontSize: 0, 37 | height: `${value * 2}${unit}`, 38 | width: `${value * 2}${unit}`, 39 | ...cssOverride, 40 | }; 41 | 42 | const ball = createAnimation( 43 | "PacmanLoader", 44 | `75% {opacity: 0.7} 45 | 100% {transform: translate(${`${-4 * value}${unit}`}, ${`${-value / 4}${unit}`})}`, 46 | "ball" 47 | ); 48 | 49 | const ballStyle = (i: number): React.CSSProperties => { 50 | return { 51 | width: `${value / 3}${unit}`, 52 | height: `${value / 3}${unit}`, 53 | backgroundColor: color, 54 | margin: cssValue(margin), 55 | borderRadius: "100%", 56 | transform: `translate(0, ${`${-value / 4}${unit}`})`, 57 | position: "absolute", 58 | top: `${value}${unit}`, 59 | left: `${value * 4}${unit}`, 60 | animation: `${ball} ${1 / speedMultiplier}s ${i * 0.25}s infinite linear`, 61 | animationFillMode: "both", 62 | }; 63 | }; 64 | 65 | const s1 = `${cssValue(size)} solid transparent`; 66 | 67 | const s2 = `${cssValue(size)} solid ${color}`; 68 | 69 | const pacmanStyle = (i: number): React.CSSProperties => { 70 | return { 71 | width: 0, 72 | height: 0, 73 | borderRight: s1, 74 | borderTop: i === 0 ? s1 : s2, 75 | borderLeft: s2, 76 | borderBottom: i === 0 ? s2 : s1, 77 | borderRadius: cssValue(size), 78 | position: "absolute", 79 | animation: `${pacman[i]} ${0.8 / speedMultiplier}s infinite ease-in-out`, 80 | animationFillMode: "both", 81 | }; 82 | }; 83 | 84 | const pac = pacmanStyle(0); 85 | const man = pacmanStyle(1); 86 | 87 | if (!loading) { 88 | return null; 89 | } 90 | 91 | return ( 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | ); 101 | } 102 | 103 | export default PacmanLoader; 104 | -------------------------------------------------------------------------------- /src/PropagateLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { parseLengthAndUnit } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | // 1.5 4.5 7.5 10 | const distance = [1, 3, 5]; 11 | 12 | const propagate = [ 13 | createAnimation( 14 | "PropagateLoader", 15 | `25% {transform: translateX(-${distance[0]}rem) scale(0.75)} 16 | 50% {transform: translateX(-${distance[1]}rem) scale(0.6)} 17 | 75% {transform: translateX(-${distance[2]}rem) scale(0.5)} 18 | 95% {transform: translateX(0rem) scale(1)}`, 19 | "propogate-0" 20 | ), 21 | createAnimation( 22 | "PropagateLoader", 23 | `25% {transform: translateX(-${distance[0]}rem) scale(0.75)} 24 | 50% {transform: translateX(-${distance[1]}rem) scale(0.6)} 25 | 75% {transform: translateX(-${distance[1]}rem) scale(0.6)} 26 | 95% {transform: translateX(0rem) scale(1)}`, 27 | "propogate-1" 28 | ), 29 | createAnimation( 30 | "PropagateLoader", 31 | `25% {transform: translateX(-${distance[0]}rem) scale(0.75)} 32 | 75% {transform: translateX(-${distance[0]}rem) scale(0.75)} 33 | 95% {transform: translateX(0rem) scale(1)}`, 34 | "propogate-2" 35 | ), 36 | createAnimation( 37 | "PropagateLoader", 38 | `25% {transform: translateX(${distance[0]}rem) scale(0.75)} 39 | 75% {transform: translateX(${distance[0]}rem) scale(0.75)} 40 | 95% {transform: translateX(0rem) scale(1)}`, 41 | "propogate-3" 42 | ), 43 | createAnimation( 44 | "PropagateLoader", 45 | `25% {transform: translateX(${distance[0]}rem) scale(0.75)} 46 | 50% {transform: translateX(${distance[1]}rem) scale(0.6)} 47 | 75% {transform: translateX(${distance[1]}rem) scale(0.6)} 48 | 95% {transform: translateX(0rem) scale(1)}`, 49 | "propogate-4" 50 | ), 51 | createAnimation( 52 | "PropagateLoader", 53 | `25% {transform: translateX(${distance[0]}rem) scale(0.75)} 54 | 50% {transform: translateX(${distance[1]}rem) scale(0.6)} 55 | 75% {transform: translateX(${distance[2]}rem) scale(0.5)} 56 | 95% {transform: translateX(0rem) scale(1)}`, 57 | "propogate-5" 58 | ), 59 | ]; 60 | 61 | function PropagateLoader({ 62 | loading = true, 63 | color = "#000000", 64 | speedMultiplier = 1, 65 | cssOverride = {}, 66 | size = 15, 67 | ...additionalprops 68 | }: LoaderSizeProps) { 69 | const { value, unit } = parseLengthAndUnit(size); 70 | 71 | const wrapper: React.CSSProperties = { 72 | display: "inherit", 73 | position: "relative", 74 | ...cssOverride, 75 | }; 76 | 77 | const style = (i: number): React.CSSProperties => { 78 | return { 79 | position: "absolute", 80 | fontSize: `${value / 3}${unit}`, 81 | width: `${value}${unit}`, 82 | height: `${value}${unit}`, 83 | background: color, 84 | borderRadius: "50%", 85 | animation: `${propagate[i]} ${1.5 / speedMultiplier}s infinite`, 86 | animationFillMode: "forwards", 87 | }; 88 | }; 89 | 90 | if (!loading) { 91 | return null; 92 | } 93 | 94 | return ( 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | ); 104 | } 105 | 106 | export default PropagateLoader; 107 | -------------------------------------------------------------------------------- /src/PuffLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const puff = [ 10 | createAnimation("PuffLoader", "0% {transform: scale(0)} 100% {transform: scale(1.0)}", "puff-1"), 11 | createAnimation("PuffLoader", "0% {opacity: 1} 100% {opacity: 0}", "puff-2"), 12 | ]; 13 | 14 | function PuffLoader({ 15 | loading = true, 16 | color = "#000000", 17 | speedMultiplier = 1, 18 | cssOverride = {}, 19 | size = 60, 20 | ...additionalprops 21 | }: LoaderSizeProps) { 22 | const wrapper: React.CSSProperties = { 23 | display: "inherit", 24 | position: "relative", 25 | width: cssValue(size), 26 | height: cssValue(size), 27 | ...cssOverride, 28 | }; 29 | 30 | const style = (i: number): React.CSSProperties => { 31 | return { 32 | position: "absolute", 33 | height: cssValue(size), 34 | width: cssValue(size), 35 | border: `thick solid ${color}`, 36 | borderRadius: "50%", 37 | opacity: "1", 38 | top: "0", 39 | left: "0", 40 | animationFillMode: "both", 41 | animation: `${puff[0]}, ${puff[1]}`, 42 | animationDuration: `${2 / speedMultiplier}s`, 43 | animationIterationCount: "infinite", 44 | animationTimingFunction: 45 | "cubic-bezier(0.165, 0.84, 0.44, 1), cubic-bezier(0.3, 0.61, 0.355, 1)", 46 | animationDelay: i === 1 ? "-1s" : "0s", 47 | }; 48 | }; 49 | 50 | if (!loading) { 51 | return null; 52 | } 53 | 54 | return ( 55 | 56 | 57 | 58 | 59 | ); 60 | } 61 | 62 | export default PuffLoader; 63 | -------------------------------------------------------------------------------- /src/PulseLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeMarginProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const pulse = createAnimation( 10 | "PulseLoader", 11 | "0% {transform: scale(1); opacity: 1} 45% {transform: scale(0.1); opacity: 0.7} 80% {transform: scale(1); opacity: 1}", 12 | "pulse" 13 | ); 14 | 15 | function PulseLoader({ 16 | loading = true, 17 | color = "#000000", 18 | speedMultiplier = 1, 19 | cssOverride = {}, 20 | size = 15, 21 | margin = 2, 22 | ...additionalprops 23 | }: LoaderSizeMarginProps) { 24 | const wrapper: React.CSSProperties = { 25 | display: "inherit", 26 | ...cssOverride, 27 | }; 28 | 29 | const style = (i: number): React.CSSProperties => { 30 | return { 31 | backgroundColor: color, 32 | width: cssValue(size), 33 | height: cssValue(size), 34 | margin: cssValue(margin), 35 | borderRadius: "100%", 36 | display: "inline-block", 37 | animation: `${pulse} ${0.75 / speedMultiplier}s ${ 38 | (i * 0.12) / speedMultiplier 39 | }s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08)`, 40 | animationFillMode: "both", 41 | }; 42 | }; 43 | 44 | if (!loading) { 45 | return null; 46 | } 47 | 48 | return ( 49 | 50 | 51 | 52 | 53 | 54 | ); 55 | } 56 | 57 | export default PulseLoader; 58 | -------------------------------------------------------------------------------- /src/RingLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { parseLengthAndUnit, cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const right = createAnimation( 10 | "RingLoader", 11 | "0% {transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg)} 100% {transform: rotateX(180deg) rotateY(360deg) rotateZ(360deg)}", 12 | "right" 13 | ); 14 | 15 | const left = createAnimation( 16 | "RingLoader", 17 | "0% {transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg)} 100% {transform: rotateX(360deg) rotateY(180deg) rotateZ(360deg)}", 18 | "left" 19 | ); 20 | 21 | function RingLoader({ 22 | loading = true, 23 | color = "#000000", 24 | speedMultiplier = 1, 25 | cssOverride = {}, 26 | size = 60, 27 | ...additionalprops 28 | }: LoaderSizeProps) { 29 | const { value, unit } = parseLengthAndUnit(size); 30 | 31 | const wrapper: React.CSSProperties = { 32 | display: "inherit", 33 | width: cssValue(size), 34 | height: cssValue(size), 35 | position: "relative", 36 | ...cssOverride, 37 | }; 38 | 39 | const style = (i: number): React.CSSProperties => { 40 | return { 41 | position: "absolute", 42 | top: "0", 43 | left: "0", 44 | width: `${value}${unit}`, 45 | height: `${value}${unit}`, 46 | border: `${value / 10}${unit} solid ${color}`, 47 | opacity: "0.4", 48 | borderRadius: "100%", 49 | animationFillMode: "forwards", 50 | perspective: "800px", 51 | animation: `${i === 1 ? right : left} ${2 / speedMultiplier}s 0s infinite linear`, 52 | }; 53 | }; 54 | 55 | if (!loading) { 56 | return null; 57 | } 58 | 59 | return ( 60 | 61 | 62 | 63 | 64 | ); 65 | } 66 | 67 | export default RingLoader; 68 | -------------------------------------------------------------------------------- /src/RiseLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeMarginProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | function RiseLoader({ 10 | loading = true, 11 | color = "#000000", 12 | speedMultiplier = 1, 13 | cssOverride = {}, 14 | size = 15, 15 | margin = 2, 16 | ...additionalprops 17 | }: LoaderSizeMarginProps) { 18 | const wrapper: React.CSSProperties = { 19 | display: "inherit", 20 | ...cssOverride, 21 | }; 22 | 23 | const even = createAnimation( 24 | "RiseLoader", 25 | `0% {transform: scale(1.1)} 26 | 25% {transform: translateY(-${size}px)} 27 | 50% {transform: scale(0.4)} 28 | 75% {transform: translateY(${size}px)} 29 | 100% {transform: translateY(0) scale(1.0)}`, 30 | "even" 31 | ); 32 | 33 | const odd = createAnimation( 34 | "RiseLoader", 35 | `0% {transform: scale(0.4)} 36 | 25% {transform: translateY(${size}px)} 37 | 50% {transform: scale(1.1)} 38 | 75% {transform: translateY(${-size}px)} 39 | 100% {transform: translateY(0) scale(0.75)}`, 40 | "odd" 41 | ); 42 | 43 | const style = (i: number): React.CSSProperties => { 44 | return { 45 | backgroundColor: color, 46 | width: cssValue(size), 47 | height: cssValue(size), 48 | margin: cssValue(margin), 49 | borderRadius: "100%", 50 | display: "inline-block", 51 | animation: `${i % 2 === 0 ? even : odd} ${1 / speedMultiplier}s 0s infinite cubic-bezier(0.15, 0.46, 0.9, 0.6)`, 52 | animationFillMode: "both", 53 | }; 54 | }; 55 | 56 | if (!loading) { 57 | return null; 58 | } 59 | 60 | return ( 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | ); 69 | } 70 | 71 | export default RiseLoader; 72 | -------------------------------------------------------------------------------- /src/RotateLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue, parseLengthAndUnit } from "./helpers/unitConverter"; 6 | import { LoaderSizeMarginProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const rotate = createAnimation( 10 | "RotateLoader", 11 | "0% {transform: rotate(0deg)} 50% {transform: rotate(180deg)} 100% {transform: rotate(360deg)}", 12 | "rotate" 13 | ); 14 | 15 | function RotateLoader({ 16 | loading = true, 17 | color = "#000000", 18 | speedMultiplier = 1, 19 | cssOverride = {}, 20 | size = 15, 21 | margin = 2, 22 | ...additionalprops 23 | }: LoaderSizeMarginProps) { 24 | const { value, unit } = parseLengthAndUnit(margin); 25 | 26 | const ball: React.CSSProperties = { 27 | backgroundColor: color, 28 | width: cssValue(size), 29 | height: cssValue(size), 30 | borderRadius: "100%", 31 | }; 32 | 33 | const wrapper: React.CSSProperties = { 34 | ...ball, 35 | display: "inline-block", 36 | position: "relative", 37 | animationFillMode: "both", 38 | animation: `${rotate} ${1 / speedMultiplier}s 0s infinite cubic-bezier(0.7, -0.13, 0.22, 0.86)`, 39 | ...cssOverride, 40 | }; 41 | 42 | const style = (i: number): React.CSSProperties => { 43 | const left = (i % 2 ? -1 : 1) * (26 + value); 44 | 45 | return { 46 | opacity: "0.8", 47 | position: "absolute", 48 | top: "0", 49 | left: `${left}${unit}`, 50 | }; 51 | }; 52 | 53 | if (!loading) { 54 | return null; 55 | } 56 | 57 | return ( 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | 65 | export default RotateLoader; 66 | -------------------------------------------------------------------------------- /src/ScaleLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { LoaderHeightWidthRadiusProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const scale = createAnimation( 10 | "ScaleLoader", 11 | "0% {transform: scaley(1.0)} 50% {transform: scaley(0.4)} 100% {transform: scaley(1.0)}", 12 | "scale" 13 | ); 14 | 15 | function ScaleLoader({ 16 | loading = true, 17 | color = "#000000", 18 | speedMultiplier = 1, 19 | cssOverride = {}, 20 | height = 35, 21 | width = 4, 22 | radius = 2, 23 | margin = 2, 24 | barCount = 5, 25 | ...additionalprops 26 | }: LoaderHeightWidthRadiusProps & { barCount?: number }) { 27 | const wrapper: React.CSSProperties = { 28 | display: "inherit", 29 | ...cssOverride, 30 | }; 31 | 32 | const style = (i: number): React.CSSProperties => { 33 | return { 34 | backgroundColor: color, 35 | width: cssValue(width), 36 | height: cssValue(height), 37 | margin: cssValue(margin), 38 | borderRadius: cssValue(radius), 39 | display: "inline-block", 40 | animation: `${scale} ${1 / speedMultiplier}s ${i * 0.1}s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08)`, 41 | animationFillMode: "both", 42 | }; 43 | }; 44 | 45 | if (!loading) { 46 | return null; 47 | } 48 | 49 | return ( 50 | 51 | {[...Array(barCount)].map((_, i) => ( 52 | 53 | ))} 54 | 55 | ); 56 | } 57 | 58 | export default ScaleLoader; 59 | -------------------------------------------------------------------------------- /src/SkewLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const skew = createAnimation( 10 | "SkewLoader", 11 | "25% {transform: perspective(100px) rotateX(180deg) rotateY(0)} 50% {transform: perspective(100px) rotateX(180deg) rotateY(180deg)} 75% {transform: perspective(100px) rotateX(0) rotateY(180deg)} 100% {transform: perspective(100px) rotateX(0) rotateY(0)}", 12 | "skew" 13 | ); 14 | 15 | function SkewLoader({ 16 | loading = true, 17 | color = "#000000", 18 | speedMultiplier = 1, 19 | cssOverride = {}, 20 | size = 20, 21 | ...additionalprops 22 | }: LoaderSizeProps) { 23 | const style: React.CSSProperties = { 24 | width: "0", 25 | height: "0", 26 | borderLeft: `${cssValue(size)} solid transparent`, 27 | borderRight: `${cssValue(size)} solid transparent`, 28 | borderBottom: `${cssValue(size)} solid ${color}`, 29 | display: "inline-block", 30 | animation: `${skew} ${3 / speedMultiplier}s 0s infinite cubic-bezier(0.09, 0.57, 0.49, 0.9)`, 31 | animationFillMode: "both", 32 | ...cssOverride, 33 | }; 34 | 35 | if (!loading) { 36 | return null; 37 | } 38 | 39 | return ; 40 | } 41 | 42 | export default SkewLoader; 43 | -------------------------------------------------------------------------------- /src/SquareLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { cssValue } from "./helpers/unitConverter"; 6 | import { LoaderSizeProps } from "./helpers/props"; 7 | import { createAnimation } from "./helpers/animation"; 8 | 9 | const square = createAnimation( 10 | "SquareLoader", 11 | `25% {transform: rotateX(180deg) rotateY(0)} 12 | 50% {transform: rotateX(180deg) rotateY(180deg)} 13 | 75% {transform: rotateX(0) rotateY(180deg)} 14 | 100% {transform: rotateX(0) rotateY(0)}`, 15 | "square" 16 | ); 17 | 18 | function SquareLoader({ 19 | loading = true, 20 | color = "#000000", 21 | speedMultiplier = 1, 22 | cssOverride = {}, 23 | size = 50, 24 | ...additionalprops 25 | }: LoaderSizeProps) { 26 | const style: React.CSSProperties = { 27 | backgroundColor: color, 28 | width: cssValue(size), 29 | height: cssValue(size), 30 | display: "inline-block", 31 | animation: `${square} ${3 / speedMultiplier}s 0s infinite cubic-bezier(0.09, 0.57, 0.49, 0.9)`, 32 | animationFillMode: "both", 33 | ...cssOverride, 34 | }; 35 | 36 | if (!loading) { 37 | return null; 38 | } 39 | 40 | return ; 41 | } 42 | 43 | export default SquareLoader; 44 | -------------------------------------------------------------------------------- /src/SyncLoader.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import { LoaderSizeMarginProps } from "./helpers/props"; 6 | import { createAnimation } from "./helpers/animation"; 7 | import { cssValue } from "./helpers/unitConverter"; 8 | 9 | const sync = createAnimation( 10 | "SyncLoader", 11 | `33% {transform: translateY(10px)} 12 | 66% {transform: translateY(-10px)} 13 | 100% {transform: translateY(0)}`, 14 | "sync" 15 | ); 16 | 17 | function SyncLoader({ 18 | loading = true, 19 | color = "#000000", 20 | speedMultiplier = 1, 21 | cssOverride = {}, 22 | size = 15, 23 | margin = 2, 24 | ...additionalprops 25 | }: LoaderSizeMarginProps) { 26 | const wrapper: React.CSSProperties = { 27 | display: "inherit", 28 | ...cssOverride, 29 | }; 30 | 31 | const style = (i: number): React.CSSProperties => { 32 | return { 33 | backgroundColor: color, 34 | width: cssValue(size), 35 | height: cssValue(size), 36 | margin: cssValue(margin), 37 | borderRadius: "100%", 38 | display: "inline-block", 39 | animation: `${sync} ${0.6 / speedMultiplier}s ${i * 0.07}s infinite ease-in-out`, 40 | animationFillMode: "both", 41 | }; 42 | }; 43 | 44 | if (!loading) { 45 | return null; 46 | } 47 | 48 | return ( 49 | 50 | 51 | 52 | 53 | 54 | ); 55 | } 56 | 57 | export default SyncLoader; 58 | -------------------------------------------------------------------------------- /src/helpers/animation.server.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 5 | import { createAnimation } from "./animation"; 6 | 7 | describe("animation", () => { 8 | it("should not throw an error on server side", () => { 9 | const name = createAnimation( 10 | "TestLoader", 11 | "0% {left: -35%;right: 100%} 60% {left: 100%;right: -90%} 100% {left: 100%;right: -90%}", 12 | "my-suffix" 13 | ); 14 | expect(name).toEqual("react-spinners-TestLoader-my-suffix"); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/helpers/animation.test.ts: -------------------------------------------------------------------------------- 1 | import { createAnimation } from "./animation"; 2 | 3 | describe("createAnimation", () => { 4 | it("should return name with suffix if passed in", () => { 5 | const name = createAnimation( 6 | "TestLoader", 7 | "0% {left: -35%;right: 100%} 60% {left: 100%;right: -90%} 100% {left: 100%;right: -90%}", 8 | "my-suffix" 9 | ); 10 | expect(name).toEqual("react-spinners-TestLoader-my-suffix"); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/helpers/animation.ts: -------------------------------------------------------------------------------- 1 | export const createAnimation = (loaderName: string, frames: string, suffix: string): string => { 2 | const animationName = `react-spinners-${loaderName}-${suffix}`; 3 | 4 | if (typeof window == "undefined" || !window.document) { 5 | return animationName; 6 | } 7 | 8 | const styleEl = document.createElement("style"); 9 | document.head.appendChild(styleEl); 10 | const styleSheet = styleEl.sheet; 11 | 12 | const keyFrames = ` 13 | @keyframes ${animationName} { 14 | ${frames} 15 | } 16 | `; 17 | 18 | if (styleSheet) { 19 | styleSheet.insertRule(keyFrames, 0); 20 | } 21 | 22 | return animationName; 23 | }; 24 | -------------------------------------------------------------------------------- /src/helpers/colors.test.ts: -------------------------------------------------------------------------------- 1 | import { calculateRgba } from "./colors"; 2 | 3 | describe("calculateRgba", () => { 4 | it("is a function", () => { 5 | expect(typeof calculateRgba).toEqual("function"); 6 | }); 7 | 8 | it("returns the same passed in rgb value with custom opacity in old syntax", () => { 9 | expect(calculateRgba("rgb(255, 255, 255, 0.5)", 1)).toEqual("rgba(255, 255, 255, 0.5)"); 10 | expect(calculateRgba("rgba(255, 255, 255, 0.5)", 1)).toEqual("rgba(255, 255, 255, 0.5)"); 11 | }); 12 | 13 | it("returns the same passed in rgb value with custom opacity in new syntax", () => { 14 | expect(calculateRgba("rgb(255 255 255 / 0.5)", 1)).toEqual("rgba(255 255 255 / 0.5)"); 15 | expect(calculateRgba("rgba(255 255 255 / 0.5)", 1)).toEqual("rgba(255 255 255 / 0.5)"); 16 | }); 17 | 18 | it("adds passed in opacity to the rgb values in old syntax", () => { 19 | expect(calculateRgba("rgb(255, 255, 255)", 0.5)).toEqual("rgba(255, 255, 255, 0.5)"); 20 | expect(calculateRgba("rgba(255, 255, 255)", 0.5)).toEqual("rgba(255, 255, 255, 0.5)"); 21 | }); 22 | 23 | it("adds passed in opacity to the rgb values in new syntax", () => { 24 | expect(calculateRgba("rgb(255 255 255)", 0.5)).toEqual("rgba(255 255 255 / 0.5)"); 25 | expect(calculateRgba("rgba(255 255 255)", 0.5)).toEqual("rgba(255 255 255 / 0.5)"); 26 | }); 27 | 28 | it("converts hash values to rgb", () => { 29 | expect(calculateRgba("#ffffff", 1)).toEqual("rgba(255, 255, 255, 1)"); 30 | }); 31 | 32 | it("calculates 3 character hash value to the correct rgba", () => { 33 | expect(calculateRgba("#fff", 1)).toEqual("rgba(255, 255, 255, 1)"); 34 | }); 35 | 36 | it("returns the passed in opacity as the part of the rgba value", () => { 37 | expect(calculateRgba("#fff", 0.5)).toEqual("rgba(255, 255, 255, 0.5)"); 38 | }); 39 | 40 | it("calculated the correct rgba value without the starting # passed in", () => { 41 | expect(calculateRgba("fff", 1)).toEqual("rgba(255, 255, 255, 1)"); 42 | }); 43 | 44 | it("calculates the correct rgba using basic color names", () => { 45 | expect(calculateRgba("maroon", 0.7)).toEqual("rgba(128, 0, 0, 0.7)"); 46 | expect(calculateRgba("red", 0.7)).toEqual("rgba(255, 0, 0, 0.7)"); 47 | expect(calculateRgba("orange", 0.7)).toEqual("rgba(255, 165, 0, 0.7)"); 48 | expect(calculateRgba("yellow", 0.7)).toEqual("rgba(255, 255, 0, 0.7)"); 49 | expect(calculateRgba("olive", 0.7)).toEqual("rgba(128, 128, 0, 0.7)"); 50 | expect(calculateRgba("green", 0.7)).toEqual("rgba(0, 128, 0, 0.7)"); 51 | expect(calculateRgba("purple", 0.7)).toEqual("rgba(128, 0, 128, 0.7)"); 52 | expect(calculateRgba("fuchsia", 0.7)).toEqual("rgba(255, 0, 255, 0.7)"); 53 | expect(calculateRgba("lime", 0.7)).toEqual("rgba(0, 255, 0, 0.7)"); 54 | expect(calculateRgba("teal", 0.7)).toEqual("rgba(0, 128, 128, 0.7)"); 55 | expect(calculateRgba("aqua", 0.7)).toEqual("rgba(0, 255, 255, 0.7)"); 56 | expect(calculateRgba("blue", 0.7)).toEqual("rgba(0, 0, 255, 0.7)"); 57 | expect(calculateRgba("navy", 0.7)).toEqual("rgba(0, 0, 128, 0.7)"); 58 | expect(calculateRgba("black", 0.7)).toEqual("rgba(0, 0, 0, 0.7)"); 59 | expect(calculateRgba("gray", 0.7)).toEqual("rgba(128, 128, 128, 0.7)"); 60 | expect(calculateRgba("silver", 0.7)).toEqual("rgba(192, 192, 192, 0.7)"); 61 | expect(calculateRgba("white", 0.7)).toEqual("rgba(255, 255, 255, 0.7)"); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/helpers/colors.ts: -------------------------------------------------------------------------------- 1 | enum BasicColors { 2 | maroon = "#800000", 3 | red = "#FF0000", 4 | orange = "#FFA500", 5 | yellow = "#FFFF00", 6 | olive = "#808000", 7 | green = "#008000", 8 | purple = "#800080", 9 | fuchsia = "#FF00FF", 10 | lime = "#00FF00", 11 | teal = "#008080", 12 | aqua = "#00FFFF", 13 | blue = "#0000FF", 14 | navy = "#000080", 15 | black = "#000000", 16 | gray = "#808080", 17 | silver = "#C0C0C0", 18 | white = "#FFFFFF", 19 | } 20 | 21 | const handleRgbColorString = (color: string, opacity: number): string => { 22 | // rgb(a)(255 255 255 / 80%) 23 | if (color.includes("/")) { 24 | return color.replace("rgb(", "rgba("); 25 | } 26 | 27 | const rgbValues = color.substring(color.startsWith("rgba(") ? 5 : 4, color.length - 1).trim(); 28 | const splittedByCommas = rgbValues.split(","); 29 | 30 | // rgb(a)(255, 255, 255, 0.8) 31 | if (splittedByCommas.length === 4) { 32 | return color.replace("rgb(", "rgba("); 33 | } 34 | 35 | // rgb(a)(255, 255, 255) 36 | if (splittedByCommas.length === 3) { 37 | return `rgba(${rgbValues}, ${opacity})`; 38 | } 39 | 40 | // rgb(a)(255 255 255) 41 | return `rgba(${rgbValues} / ${opacity})`; 42 | }; 43 | 44 | export const calculateRgba = (color: string, opacity: number): string => { 45 | if (color.startsWith("rgb")) { 46 | return handleRgbColorString(color, opacity); 47 | } 48 | 49 | if (Object.keys(BasicColors).includes(color)) { 50 | color = BasicColors[color as keyof typeof BasicColors]; 51 | } 52 | 53 | if (color[0] === "#") { 54 | color = color.slice(1); 55 | } 56 | 57 | if (color.length === 3) { 58 | let res = ""; 59 | color.split("").forEach((c: string) => { 60 | res += c; 61 | res += c; 62 | }); 63 | color = res; 64 | } 65 | 66 | const rgbValues: string = (color.match(/.{2}/g) || []).map((hex: string) => parseInt(hex, 16)).join(", "); 67 | 68 | return `rgba(${rgbValues}, ${opacity})`; 69 | }; 70 | -------------------------------------------------------------------------------- /src/helpers/props.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, DetailedHTMLProps, HTMLAttributes } from "react"; 2 | 3 | export type LengthType = number | string; 4 | 5 | interface CommonProps extends DetailedHTMLProps, HTMLSpanElement> { 6 | color?: string; 7 | loading?: boolean; 8 | cssOverride?: CSSProperties; 9 | speedMultiplier?: number; 10 | } 11 | 12 | export interface LoaderHeightWidthProps extends CommonProps { 13 | height?: LengthType; 14 | width?: LengthType; 15 | } 16 | 17 | export interface LoaderSizeProps extends CommonProps { 18 | size?: LengthType; 19 | } 20 | 21 | export interface LoaderSizeMarginProps extends CommonProps { 22 | size?: LengthType; 23 | margin?: LengthType; 24 | } 25 | 26 | export interface LoaderHeightWidthRadiusProps extends CommonProps { 27 | height?: LengthType; 28 | width?: LengthType; 29 | radius?: LengthType; 30 | margin?: LengthType; 31 | } 32 | -------------------------------------------------------------------------------- /src/helpers/unitConverter.test.ts: -------------------------------------------------------------------------------- 1 | import { parseLengthAndUnit, cssValue } from "./unitConverter"; 2 | 3 | describe("unitConverter", () => { 4 | describe("parseLengthAndUnit", () => { 5 | const spy = jest.spyOn(console, "warn").mockImplementation(); 6 | const output = { 7 | value: 12, 8 | unit: "px", 9 | }; 10 | 11 | it("is a function", () => { 12 | expect(typeof parseLengthAndUnit).toEqual("function"); 13 | }); 14 | 15 | it("takes a number as the input and append px to the value", () => { 16 | expect(parseLengthAndUnit(12)).toEqual(output); 17 | expect(spy).not.toBeCalled(); 18 | }); 19 | 20 | it("take a string with valid integer css unit and return an object with value and unit", () => { 21 | expect(parseLengthAndUnit("12px")).toEqual(output); 22 | expect(spy).not.toBeCalled(); 23 | }); 24 | 25 | it("take a string with valid css float unit and return an object with value and unit", () => { 26 | const output = { 27 | value: 12.5, 28 | unit: "px", 29 | }; 30 | expect(parseLengthAndUnit("12.5px")).toEqual(output); 31 | expect(spy).not.toBeCalled(); 32 | }); 33 | 34 | it("takes an invalid css unit and default the value to px", () => { 35 | expect(parseLengthAndUnit("12fd")).toEqual(output); 36 | expect(spy).toBeCalled(); 37 | }); 38 | }); 39 | 40 | describe("cssValue", () => { 41 | it("is a function", () => { 42 | expect(typeof cssValue).toEqual("function"); 43 | }); 44 | 45 | it("takes a number as the input and append px to the value", () => { 46 | expect(cssValue(12)).toEqual("12px"); 47 | }); 48 | 49 | it("takes a string with valid css unit as the input and return the value", () => { 50 | expect(cssValue("12%")).toEqual("12%"); 51 | expect(cssValue("12em")).toEqual("12em"); 52 | }); 53 | 54 | it("takes a string with invalid css unit as the input and default to px", () => { 55 | expect(cssValue("12qw")).toEqual("12px"); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/helpers/unitConverter.ts: -------------------------------------------------------------------------------- 1 | interface LengthObject { 2 | value: number; 3 | unit: string; 4 | } 5 | 6 | const cssUnit: { [unit: string]: boolean } = { 7 | cm: true, 8 | mm: true, 9 | in: true, 10 | px: true, 11 | pt: true, 12 | pc: true, 13 | em: true, 14 | ex: true, 15 | ch: true, 16 | rem: true, 17 | vw: true, 18 | vh: true, 19 | vmin: true, 20 | vmax: true, 21 | "%": true, 22 | }; 23 | 24 | /** 25 | * If size is a number, append px to the value as default unit. 26 | * If size is a string, validate against list of valid units. 27 | * If unit is valid, return size as is. 28 | * If unit is invalid, console warn issue, replace with px as the unit. 29 | * 30 | * @param {(number | string)} size 31 | * @return {LengthObject} LengthObject 32 | */ 33 | export function parseLengthAndUnit(size: number | string): LengthObject { 34 | if (typeof size === "number") { 35 | return { 36 | value: size, 37 | unit: "px", 38 | }; 39 | } 40 | let value: number; 41 | const valueString: string = (size.match(/^[0-9.]*/) || "").toString(); 42 | if (valueString.includes(".")) { 43 | value = parseFloat(valueString); 44 | } else { 45 | value = parseInt(valueString, 10); 46 | } 47 | 48 | const unit: string = (size.match(/[^0-9]*$/) || "").toString(); 49 | 50 | if (cssUnit[unit]) { 51 | return { 52 | value, 53 | unit, 54 | }; 55 | } 56 | 57 | console.warn(`React Spinners: ${size} is not a valid css value. Defaulting to ${value}px.`); 58 | 59 | return { 60 | value, 61 | unit: "px", 62 | }; 63 | } 64 | 65 | /** 66 | * Take value as an input and return valid css value 67 | * 68 | * @param {(number | string)} value 69 | * @return {string} valid css value 70 | */ 71 | export function cssValue(value: number | string): string { 72 | const lengthWithunit = parseLengthAndUnit(value); 73 | 74 | return `${lengthWithunit.value}${lengthWithunit.unit}`; 75 | } 76 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BarLoader } from "./BarLoader"; 2 | export { default as BeatLoader } from "./BeatLoader"; 3 | export { default as BounceLoader } from "./BounceLoader"; 4 | export { default as CircleLoader } from "./CircleLoader"; 5 | export { default as ClimbingBoxLoader } from "./ClimbingBoxLoader"; 6 | export { default as ClipLoader } from "./ClipLoader"; 7 | export { default as ClockLoader } from "./ClockLoader"; 8 | export { default as DotLoader } from "./DotLoader"; 9 | export { default as FadeLoader } from "./FadeLoader"; 10 | export { default as GridLoader } from "./GridLoader"; 11 | export { default as HashLoader } from "./HashLoader"; 12 | export { default as MoonLoader } from "./MoonLoader"; 13 | export { default as PacmanLoader } from "./PacmanLoader"; 14 | export { default as PropagateLoader } from "./PropagateLoader"; 15 | export { default as PulseLoader } from "./PulseLoader"; 16 | export { default as PuffLoader } from "./PuffLoader"; 17 | export { default as RingLoader } from "./RingLoader"; 18 | export { default as RiseLoader } from "./RiseLoader"; 19 | export { default as RotateLoader } from "./RotateLoader"; 20 | export { default as ScaleLoader } from "./ScaleLoader"; 21 | export { default as SkewLoader } from "./SkewLoader"; 22 | export { default as SquareLoader } from "./SquareLoader"; 23 | export { default as SyncLoader } from "./SyncLoader"; 24 | -------------------------------------------------------------------------------- /stories/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidhu2000/react-spinners/bbfedfb5310996c70365755046632e4882e250c7/stories/.keep -------------------------------------------------------------------------------- /test-apps/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /test-apps/nextjs/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /test-apps/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "15.3.0", 13 | "react": "^19.0.0", 14 | "react-dom": "^19.0.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^20", 18 | "@types/react": "^19", 19 | "@types/react-dom": "^19", 20 | "typescript": "^5" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test-apps/nextjs/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Next.js', 3 | description: 'Generated by Next.js', 4 | } 5 | 6 | export default function RootLayout({ 7 | children, 8 | }: { 9 | children: React.ReactNode 10 | }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /test-apps/nextjs/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import * as Loaders from "esm/index"; 2 | 3 | export default function Home() { 4 | return ( 5 |
15 |

Test App

16 | 17 | 18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /test-apps/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"], 23 | "cjs/*": ["../../cjs/*"], 24 | "esm/*": ["../../esm/*"], 25 | "src/*": ["../../src/*"] 26 | } 27 | }, 28 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /test-apps/nextjs/yarn.lock: -------------------------------------------------------------------------------- 1 | # This file is generated by running "yarn install" inside your project. 2 | # Manual changes might be lost - proceed with caution! 3 | 4 | __metadata: 5 | version: 8 6 | cacheKey: 10 7 | 8 | "@emnapi/runtime@npm:^1.4.0": 9 | version: 1.4.1 10 | resolution: "@emnapi/runtime@npm:1.4.1" 11 | dependencies: 12 | tslib: "npm:^2.4.0" 13 | checksum: 10/594ea5f45d5ecb72113fc707b997b9c2452329d0e358931c5a769aefabaf85ce7db213a85cef9140f4c69eb5fb3adf825d63e37c2188cec5ff73354c6e220a8c 14 | languageName: node 15 | linkType: hard 16 | 17 | "@img/sharp-darwin-arm64@npm:0.34.1": 18 | version: 0.34.1 19 | resolution: "@img/sharp-darwin-arm64@npm:0.34.1" 20 | dependencies: 21 | "@img/sharp-libvips-darwin-arm64": "npm:1.1.0" 22 | dependenciesMeta: 23 | "@img/sharp-libvips-darwin-arm64": 24 | optional: true 25 | conditions: os=darwin & cpu=arm64 26 | languageName: node 27 | linkType: hard 28 | 29 | "@img/sharp-darwin-x64@npm:0.34.1": 30 | version: 0.34.1 31 | resolution: "@img/sharp-darwin-x64@npm:0.34.1" 32 | dependencies: 33 | "@img/sharp-libvips-darwin-x64": "npm:1.1.0" 34 | dependenciesMeta: 35 | "@img/sharp-libvips-darwin-x64": 36 | optional: true 37 | conditions: os=darwin & cpu=x64 38 | languageName: node 39 | linkType: hard 40 | 41 | "@img/sharp-libvips-darwin-arm64@npm:1.1.0": 42 | version: 1.1.0 43 | resolution: "@img/sharp-libvips-darwin-arm64@npm:1.1.0" 44 | conditions: os=darwin & cpu=arm64 45 | languageName: node 46 | linkType: hard 47 | 48 | "@img/sharp-libvips-darwin-x64@npm:1.1.0": 49 | version: 1.1.0 50 | resolution: "@img/sharp-libvips-darwin-x64@npm:1.1.0" 51 | conditions: os=darwin & cpu=x64 52 | languageName: node 53 | linkType: hard 54 | 55 | "@img/sharp-libvips-linux-arm64@npm:1.1.0": 56 | version: 1.1.0 57 | resolution: "@img/sharp-libvips-linux-arm64@npm:1.1.0" 58 | conditions: os=linux & cpu=arm64 & libc=glibc 59 | languageName: node 60 | linkType: hard 61 | 62 | "@img/sharp-libvips-linux-arm@npm:1.1.0": 63 | version: 1.1.0 64 | resolution: "@img/sharp-libvips-linux-arm@npm:1.1.0" 65 | conditions: os=linux & cpu=arm & libc=glibc 66 | languageName: node 67 | linkType: hard 68 | 69 | "@img/sharp-libvips-linux-ppc64@npm:1.1.0": 70 | version: 1.1.0 71 | resolution: "@img/sharp-libvips-linux-ppc64@npm:1.1.0" 72 | conditions: os=linux & cpu=ppc64 & libc=glibc 73 | languageName: node 74 | linkType: hard 75 | 76 | "@img/sharp-libvips-linux-s390x@npm:1.1.0": 77 | version: 1.1.0 78 | resolution: "@img/sharp-libvips-linux-s390x@npm:1.1.0" 79 | conditions: os=linux & cpu=s390x & libc=glibc 80 | languageName: node 81 | linkType: hard 82 | 83 | "@img/sharp-libvips-linux-x64@npm:1.1.0": 84 | version: 1.1.0 85 | resolution: "@img/sharp-libvips-linux-x64@npm:1.1.0" 86 | conditions: os=linux & cpu=x64 & libc=glibc 87 | languageName: node 88 | linkType: hard 89 | 90 | "@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0": 91 | version: 1.1.0 92 | resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0" 93 | conditions: os=linux & cpu=arm64 & libc=musl 94 | languageName: node 95 | linkType: hard 96 | 97 | "@img/sharp-libvips-linuxmusl-x64@npm:1.1.0": 98 | version: 1.1.0 99 | resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.1.0" 100 | conditions: os=linux & cpu=x64 & libc=musl 101 | languageName: node 102 | linkType: hard 103 | 104 | "@img/sharp-linux-arm64@npm:0.34.1": 105 | version: 0.34.1 106 | resolution: "@img/sharp-linux-arm64@npm:0.34.1" 107 | dependencies: 108 | "@img/sharp-libvips-linux-arm64": "npm:1.1.0" 109 | dependenciesMeta: 110 | "@img/sharp-libvips-linux-arm64": 111 | optional: true 112 | conditions: os=linux & cpu=arm64 & libc=glibc 113 | languageName: node 114 | linkType: hard 115 | 116 | "@img/sharp-linux-arm@npm:0.34.1": 117 | version: 0.34.1 118 | resolution: "@img/sharp-linux-arm@npm:0.34.1" 119 | dependencies: 120 | "@img/sharp-libvips-linux-arm": "npm:1.1.0" 121 | dependenciesMeta: 122 | "@img/sharp-libvips-linux-arm": 123 | optional: true 124 | conditions: os=linux & cpu=arm & libc=glibc 125 | languageName: node 126 | linkType: hard 127 | 128 | "@img/sharp-linux-s390x@npm:0.34.1": 129 | version: 0.34.1 130 | resolution: "@img/sharp-linux-s390x@npm:0.34.1" 131 | dependencies: 132 | "@img/sharp-libvips-linux-s390x": "npm:1.1.0" 133 | dependenciesMeta: 134 | "@img/sharp-libvips-linux-s390x": 135 | optional: true 136 | conditions: os=linux & cpu=s390x & libc=glibc 137 | languageName: node 138 | linkType: hard 139 | 140 | "@img/sharp-linux-x64@npm:0.34.1": 141 | version: 0.34.1 142 | resolution: "@img/sharp-linux-x64@npm:0.34.1" 143 | dependencies: 144 | "@img/sharp-libvips-linux-x64": "npm:1.1.0" 145 | dependenciesMeta: 146 | "@img/sharp-libvips-linux-x64": 147 | optional: true 148 | conditions: os=linux & cpu=x64 & libc=glibc 149 | languageName: node 150 | linkType: hard 151 | 152 | "@img/sharp-linuxmusl-arm64@npm:0.34.1": 153 | version: 0.34.1 154 | resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.1" 155 | dependencies: 156 | "@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0" 157 | dependenciesMeta: 158 | "@img/sharp-libvips-linuxmusl-arm64": 159 | optional: true 160 | conditions: os=linux & cpu=arm64 & libc=musl 161 | languageName: node 162 | linkType: hard 163 | 164 | "@img/sharp-linuxmusl-x64@npm:0.34.1": 165 | version: 0.34.1 166 | resolution: "@img/sharp-linuxmusl-x64@npm:0.34.1" 167 | dependencies: 168 | "@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0" 169 | dependenciesMeta: 170 | "@img/sharp-libvips-linuxmusl-x64": 171 | optional: true 172 | conditions: os=linux & cpu=x64 & libc=musl 173 | languageName: node 174 | linkType: hard 175 | 176 | "@img/sharp-wasm32@npm:0.34.1": 177 | version: 0.34.1 178 | resolution: "@img/sharp-wasm32@npm:0.34.1" 179 | dependencies: 180 | "@emnapi/runtime": "npm:^1.4.0" 181 | conditions: cpu=wasm32 182 | languageName: node 183 | linkType: hard 184 | 185 | "@img/sharp-win32-ia32@npm:0.34.1": 186 | version: 0.34.1 187 | resolution: "@img/sharp-win32-ia32@npm:0.34.1" 188 | conditions: os=win32 & cpu=ia32 189 | languageName: node 190 | linkType: hard 191 | 192 | "@img/sharp-win32-x64@npm:0.34.1": 193 | version: 0.34.1 194 | resolution: "@img/sharp-win32-x64@npm:0.34.1" 195 | conditions: os=win32 & cpu=x64 196 | languageName: node 197 | linkType: hard 198 | 199 | "@next/env@npm:15.3.0": 200 | version: 15.3.0 201 | resolution: "@next/env@npm:15.3.0" 202 | checksum: 10/bc86c00d7c8abe07348bb97d9e374e652765fb244693b4e1975eb99e2f10897b0217f2cb11bca2693eb84789055945adfbd073c4e18ba5291c8a643d992f4b44 203 | languageName: node 204 | linkType: hard 205 | 206 | "@next/swc-darwin-arm64@npm:15.3.0": 207 | version: 15.3.0 208 | resolution: "@next/swc-darwin-arm64@npm:15.3.0" 209 | conditions: os=darwin & cpu=arm64 210 | languageName: node 211 | linkType: hard 212 | 213 | "@next/swc-darwin-x64@npm:15.3.0": 214 | version: 15.3.0 215 | resolution: "@next/swc-darwin-x64@npm:15.3.0" 216 | conditions: os=darwin & cpu=x64 217 | languageName: node 218 | linkType: hard 219 | 220 | "@next/swc-linux-arm64-gnu@npm:15.3.0": 221 | version: 15.3.0 222 | resolution: "@next/swc-linux-arm64-gnu@npm:15.3.0" 223 | conditions: os=linux & cpu=arm64 & libc=glibc 224 | languageName: node 225 | linkType: hard 226 | 227 | "@next/swc-linux-arm64-musl@npm:15.3.0": 228 | version: 15.3.0 229 | resolution: "@next/swc-linux-arm64-musl@npm:15.3.0" 230 | conditions: os=linux & cpu=arm64 & libc=musl 231 | languageName: node 232 | linkType: hard 233 | 234 | "@next/swc-linux-x64-gnu@npm:15.3.0": 235 | version: 15.3.0 236 | resolution: "@next/swc-linux-x64-gnu@npm:15.3.0" 237 | conditions: os=linux & cpu=x64 & libc=glibc 238 | languageName: node 239 | linkType: hard 240 | 241 | "@next/swc-linux-x64-musl@npm:15.3.0": 242 | version: 15.3.0 243 | resolution: "@next/swc-linux-x64-musl@npm:15.3.0" 244 | conditions: os=linux & cpu=x64 & libc=musl 245 | languageName: node 246 | linkType: hard 247 | 248 | "@next/swc-win32-arm64-msvc@npm:15.3.0": 249 | version: 15.3.0 250 | resolution: "@next/swc-win32-arm64-msvc@npm:15.3.0" 251 | conditions: os=win32 & cpu=arm64 252 | languageName: node 253 | linkType: hard 254 | 255 | "@next/swc-win32-x64-msvc@npm:15.3.0": 256 | version: 15.3.0 257 | resolution: "@next/swc-win32-x64-msvc@npm:15.3.0" 258 | conditions: os=win32 & cpu=x64 259 | languageName: node 260 | linkType: hard 261 | 262 | "@swc/counter@npm:0.1.3": 263 | version: 0.1.3 264 | resolution: "@swc/counter@npm:0.1.3" 265 | checksum: 10/df8f9cfba9904d3d60f511664c70d23bb323b3a0803ec9890f60133954173047ba9bdeabce28cd70ba89ccd3fd6c71c7b0bd58be85f611e1ffbe5d5c18616598 266 | languageName: node 267 | linkType: hard 268 | 269 | "@swc/helpers@npm:0.5.15": 270 | version: 0.5.15 271 | resolution: "@swc/helpers@npm:0.5.15" 272 | dependencies: 273 | tslib: "npm:^2.8.0" 274 | checksum: 10/e3f32c6deeecfb0fa3f22edff03a7b358e7ce16d27b0f1c8b5cdc3042c5c4ce4da6eac0b781ab7cc4f54696ece657467d56734fb26883439fb00017385364c4c 275 | languageName: node 276 | linkType: hard 277 | 278 | "@types/node@npm:^20": 279 | version: 20.17.30 280 | resolution: "@types/node@npm:20.17.30" 281 | dependencies: 282 | undici-types: "npm:~6.19.2" 283 | checksum: 10/69fd3b177417be77b459e8f1dd4e78c85c686167086920fbf35a9fda301709bbeee6a87ad2591fb1ddd96c65e725ec6bb527a06496626a1c94367d1361048f8d 284 | languageName: node 285 | linkType: hard 286 | 287 | "@types/react-dom@npm:^19": 288 | version: 19.1.2 289 | resolution: "@types/react-dom@npm:19.1.2" 290 | peerDependencies: 291 | "@types/react": ^19.0.0 292 | checksum: 10/bcadf2a1f4d23db8c1aaa0d13e30866d7bcda076801bbc66df14512932e0802ddac526bf15f4cce9777cd6e0bd3803aba47a1d726977f5193b4bc173348508d0 293 | languageName: node 294 | linkType: hard 295 | 296 | "@types/react@npm:^19": 297 | version: 19.1.2 298 | resolution: "@types/react@npm:19.1.2" 299 | dependencies: 300 | csstype: "npm:^3.0.2" 301 | checksum: 10/17803797227d2fc07a2cd6c17d57b1ea9b01eb16eca6318be60852c8d7467b4b58e675742f53d77ff4a37621a5814f16847dede73999181cb7f9449c1784fab6 302 | languageName: node 303 | linkType: hard 304 | 305 | "busboy@npm:1.6.0": 306 | version: 1.6.0 307 | resolution: "busboy@npm:1.6.0" 308 | dependencies: 309 | streamsearch: "npm:^1.1.0" 310 | checksum: 10/bee10fa10ea58e7e3e7489ffe4bda6eacd540a17de9f9cd21cc37e297b2dd9fe52b2715a5841afaec82900750d810d01d7edb4b2d456427f449b92b417579763 311 | languageName: node 312 | linkType: hard 313 | 314 | "caniuse-lite@npm:^1.0.30001579": 315 | version: 1.0.30001713 316 | resolution: "caniuse-lite@npm:1.0.30001713" 317 | checksum: 10/0c1b97320d08cf87c73883fe9a7b9d8037b7c24aa027641e75cce3dee74c9ad538cf17725fdcb46e6ec86be8efedf6edd0848db07c5f8110936ccf0185e2ff68 318 | languageName: node 319 | linkType: hard 320 | 321 | "client-only@npm:0.0.1": 322 | version: 0.0.1 323 | resolution: "client-only@npm:0.0.1" 324 | checksum: 10/0c16bf660dadb90610553c1d8946a7fdfb81d624adea073b8440b7d795d5b5b08beb3c950c6a2cf16279365a3265158a236876d92bce16423c485c322d7dfaf8 325 | languageName: node 326 | linkType: hard 327 | 328 | "color-convert@npm:^2.0.1": 329 | version: 2.0.1 330 | resolution: "color-convert@npm:2.0.1" 331 | dependencies: 332 | color-name: "npm:~1.1.4" 333 | checksum: 10/fa00c91b4332b294de06b443923246bccebe9fab1b253f7fe1772d37b06a2269b4039a85e309abe1fe11b267b11c08d1d0473fda3badd6167f57313af2887a64 334 | languageName: node 335 | linkType: hard 336 | 337 | "color-name@npm:^1.0.0, color-name@npm:~1.1.4": 338 | version: 1.1.4 339 | resolution: "color-name@npm:1.1.4" 340 | checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 341 | languageName: node 342 | linkType: hard 343 | 344 | "color-string@npm:^1.9.0": 345 | version: 1.9.1 346 | resolution: "color-string@npm:1.9.1" 347 | dependencies: 348 | color-name: "npm:^1.0.0" 349 | simple-swizzle: "npm:^0.2.2" 350 | checksum: 10/72aa0b81ee71b3f4fb1ac9cd839cdbd7a011a7d318ef58e6cb13b3708dca75c7e45029697260488709f1b1c7ac4e35489a87e528156c1e365917d1c4ccb9b9cd 351 | languageName: node 352 | linkType: hard 353 | 354 | "color@npm:^4.2.3": 355 | version: 4.2.3 356 | resolution: "color@npm:4.2.3" 357 | dependencies: 358 | color-convert: "npm:^2.0.1" 359 | color-string: "npm:^1.9.0" 360 | checksum: 10/b23f5e500a79ea22428db43d1a70642d983405c0dd1f95ef59dbdb9ba66afbb4773b334fa0b75bb10b0552fd7534c6b28d4db0a8b528f91975976e70973c0152 361 | languageName: node 362 | linkType: hard 363 | 364 | "csstype@npm:^3.0.2": 365 | version: 3.1.3 366 | resolution: "csstype@npm:3.1.3" 367 | checksum: 10/f593cce41ff5ade23f44e77521e3a1bcc2c64107041e1bf6c3c32adc5187d0d60983292fda326154d20b01079e24931aa5b08e4467cc488b60bb1e7f6d478ade 368 | languageName: node 369 | linkType: hard 370 | 371 | "detect-libc@npm:^2.0.3": 372 | version: 2.0.3 373 | resolution: "detect-libc@npm:2.0.3" 374 | checksum: 10/b4ea018d623e077bd395f168a9e81db77370dde36a5b01d067f2ad7989924a81d31cb547ff764acb2aa25d50bb7fdde0b0a93bec02212b0cb430621623246d39 375 | languageName: node 376 | linkType: hard 377 | 378 | "is-arrayish@npm:^0.3.1": 379 | version: 0.3.2 380 | resolution: "is-arrayish@npm:0.3.2" 381 | checksum: 10/81a78d518ebd8b834523e25d102684ee0f7e98637136d3bdc93fd09636350fa06f1d8ca997ea28143d4d13cb1b69c0824f082db0ac13e1ab3311c10ffea60ade 382 | languageName: node 383 | linkType: hard 384 | 385 | "nanoid@npm:^3.3.6": 386 | version: 3.3.11 387 | resolution: "nanoid@npm:3.3.11" 388 | bin: 389 | nanoid: bin/nanoid.cjs 390 | checksum: 10/73b5afe5975a307aaa3c95dfe3334c52cdf9ae71518176895229b8d65ab0d1c0417dd081426134eb7571c055720428ea5d57c645138161e7d10df80815527c48 391 | languageName: node 392 | linkType: hard 393 | 394 | "next@npm:15.3.0": 395 | version: 15.3.0 396 | resolution: "next@npm:15.3.0" 397 | dependencies: 398 | "@next/env": "npm:15.3.0" 399 | "@next/swc-darwin-arm64": "npm:15.3.0" 400 | "@next/swc-darwin-x64": "npm:15.3.0" 401 | "@next/swc-linux-arm64-gnu": "npm:15.3.0" 402 | "@next/swc-linux-arm64-musl": "npm:15.3.0" 403 | "@next/swc-linux-x64-gnu": "npm:15.3.0" 404 | "@next/swc-linux-x64-musl": "npm:15.3.0" 405 | "@next/swc-win32-arm64-msvc": "npm:15.3.0" 406 | "@next/swc-win32-x64-msvc": "npm:15.3.0" 407 | "@swc/counter": "npm:0.1.3" 408 | "@swc/helpers": "npm:0.5.15" 409 | busboy: "npm:1.6.0" 410 | caniuse-lite: "npm:^1.0.30001579" 411 | postcss: "npm:8.4.31" 412 | sharp: "npm:^0.34.1" 413 | styled-jsx: "npm:5.1.6" 414 | peerDependencies: 415 | "@opentelemetry/api": ^1.1.0 416 | "@playwright/test": ^1.41.2 417 | babel-plugin-react-compiler: "*" 418 | react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 419 | react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 420 | sass: ^1.3.0 421 | dependenciesMeta: 422 | "@next/swc-darwin-arm64": 423 | optional: true 424 | "@next/swc-darwin-x64": 425 | optional: true 426 | "@next/swc-linux-arm64-gnu": 427 | optional: true 428 | "@next/swc-linux-arm64-musl": 429 | optional: true 430 | "@next/swc-linux-x64-gnu": 431 | optional: true 432 | "@next/swc-linux-x64-musl": 433 | optional: true 434 | "@next/swc-win32-arm64-msvc": 435 | optional: true 436 | "@next/swc-win32-x64-msvc": 437 | optional: true 438 | sharp: 439 | optional: true 440 | peerDependenciesMeta: 441 | "@opentelemetry/api": 442 | optional: true 443 | "@playwright/test": 444 | optional: true 445 | babel-plugin-react-compiler: 446 | optional: true 447 | sass: 448 | optional: true 449 | bin: 450 | next: dist/bin/next 451 | checksum: 10/4e89943a564f4b80624a7bbd23364bd803ecebd214751809527473bb588b83c485918bf5cdf28cc6bbca83a994dd89a8e95ca5c5665e0b5e48ad10d9f3061bbc 452 | languageName: node 453 | linkType: hard 454 | 455 | "nextjs@workspace:.": 456 | version: 0.0.0-use.local 457 | resolution: "nextjs@workspace:." 458 | dependencies: 459 | "@types/node": "npm:^20" 460 | "@types/react": "npm:^19" 461 | "@types/react-dom": "npm:^19" 462 | next: "npm:15.3.0" 463 | react: "npm:^19.0.0" 464 | react-dom: "npm:^19.0.0" 465 | typescript: "npm:^5" 466 | languageName: unknown 467 | linkType: soft 468 | 469 | "picocolors@npm:^1.0.0": 470 | version: 1.1.1 471 | resolution: "picocolors@npm:1.1.1" 472 | checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 473 | languageName: node 474 | linkType: hard 475 | 476 | "postcss@npm:8.4.31": 477 | version: 8.4.31 478 | resolution: "postcss@npm:8.4.31" 479 | dependencies: 480 | nanoid: "npm:^3.3.6" 481 | picocolors: "npm:^1.0.0" 482 | source-map-js: "npm:^1.0.2" 483 | checksum: 10/1a6653e72105907377f9d4f2cd341d8d90e3fde823a5ddea1e2237aaa56933ea07853f0f2758c28892a1d70c53bbaca200eb8b80f8ed55f13093003dbec5afa0 484 | languageName: node 485 | linkType: hard 486 | 487 | "react-dom@npm:^19.0.0": 488 | version: 19.1.0 489 | resolution: "react-dom@npm:19.1.0" 490 | dependencies: 491 | scheduler: "npm:^0.26.0" 492 | peerDependencies: 493 | react: ^19.1.0 494 | checksum: 10/c5b58605862c7b0bb044416b01c73647bb8e89717fb5d7a2c279b11815fb7b49b619fe685c404e59f55eb52c66831236cc565c25ee1c2d042739f4a2cc538aa2 495 | languageName: node 496 | linkType: hard 497 | 498 | "react@npm:^19.0.0": 499 | version: 19.1.0 500 | resolution: "react@npm:19.1.0" 501 | checksum: 10/d0180689826fd9de87e839c365f6f361c561daea397d61d724687cae88f432a307d1c0f53a0ee95ddbe3352c10dac41d7ff1ad85530fb24951b27a39e5398db4 502 | languageName: node 503 | linkType: hard 504 | 505 | "scheduler@npm:^0.26.0": 506 | version: 0.26.0 507 | resolution: "scheduler@npm:0.26.0" 508 | checksum: 10/1ecf2e5d7de1a7a132796834afe14a2d589ba7e437615bd8c06f3e0786a3ac3434655e67aac8755d9b14e05754c177e49c064261de2673aaa3c926bc98caa002 509 | languageName: node 510 | linkType: hard 511 | 512 | "semver@npm:^7.7.1": 513 | version: 7.7.1 514 | resolution: "semver@npm:7.7.1" 515 | bin: 516 | semver: bin/semver.js 517 | checksum: 10/4cfa1eb91ef3751e20fc52e47a935a0118d56d6f15a837ab814da0c150778ba2ca4f1a4d9068b33070ea4273629e615066664c2cfcd7c272caf7a8a0f6518b2c 518 | languageName: node 519 | linkType: hard 520 | 521 | "sharp@npm:^0.34.1": 522 | version: 0.34.1 523 | resolution: "sharp@npm:0.34.1" 524 | dependencies: 525 | "@img/sharp-darwin-arm64": "npm:0.34.1" 526 | "@img/sharp-darwin-x64": "npm:0.34.1" 527 | "@img/sharp-libvips-darwin-arm64": "npm:1.1.0" 528 | "@img/sharp-libvips-darwin-x64": "npm:1.1.0" 529 | "@img/sharp-libvips-linux-arm": "npm:1.1.0" 530 | "@img/sharp-libvips-linux-arm64": "npm:1.1.0" 531 | "@img/sharp-libvips-linux-ppc64": "npm:1.1.0" 532 | "@img/sharp-libvips-linux-s390x": "npm:1.1.0" 533 | "@img/sharp-libvips-linux-x64": "npm:1.1.0" 534 | "@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0" 535 | "@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0" 536 | "@img/sharp-linux-arm": "npm:0.34.1" 537 | "@img/sharp-linux-arm64": "npm:0.34.1" 538 | "@img/sharp-linux-s390x": "npm:0.34.1" 539 | "@img/sharp-linux-x64": "npm:0.34.1" 540 | "@img/sharp-linuxmusl-arm64": "npm:0.34.1" 541 | "@img/sharp-linuxmusl-x64": "npm:0.34.1" 542 | "@img/sharp-wasm32": "npm:0.34.1" 543 | "@img/sharp-win32-ia32": "npm:0.34.1" 544 | "@img/sharp-win32-x64": "npm:0.34.1" 545 | color: "npm:^4.2.3" 546 | detect-libc: "npm:^2.0.3" 547 | semver: "npm:^7.7.1" 548 | dependenciesMeta: 549 | "@img/sharp-darwin-arm64": 550 | optional: true 551 | "@img/sharp-darwin-x64": 552 | optional: true 553 | "@img/sharp-libvips-darwin-arm64": 554 | optional: true 555 | "@img/sharp-libvips-darwin-x64": 556 | optional: true 557 | "@img/sharp-libvips-linux-arm": 558 | optional: true 559 | "@img/sharp-libvips-linux-arm64": 560 | optional: true 561 | "@img/sharp-libvips-linux-ppc64": 562 | optional: true 563 | "@img/sharp-libvips-linux-s390x": 564 | optional: true 565 | "@img/sharp-libvips-linux-x64": 566 | optional: true 567 | "@img/sharp-libvips-linuxmusl-arm64": 568 | optional: true 569 | "@img/sharp-libvips-linuxmusl-x64": 570 | optional: true 571 | "@img/sharp-linux-arm": 572 | optional: true 573 | "@img/sharp-linux-arm64": 574 | optional: true 575 | "@img/sharp-linux-s390x": 576 | optional: true 577 | "@img/sharp-linux-x64": 578 | optional: true 579 | "@img/sharp-linuxmusl-arm64": 580 | optional: true 581 | "@img/sharp-linuxmusl-x64": 582 | optional: true 583 | "@img/sharp-wasm32": 584 | optional: true 585 | "@img/sharp-win32-ia32": 586 | optional: true 587 | "@img/sharp-win32-x64": 588 | optional: true 589 | checksum: 10/aecb960c0780b56134bfef01b7aeaa4e6650320a8a1f491237b45e900fc670830ee5d0600f30e51878328109db82e376bb526931d07a2e9358510ef30ab5abe8 590 | languageName: node 591 | linkType: hard 592 | 593 | "simple-swizzle@npm:^0.2.2": 594 | version: 0.2.2 595 | resolution: "simple-swizzle@npm:0.2.2" 596 | dependencies: 597 | is-arrayish: "npm:^0.3.1" 598 | checksum: 10/c6dffff17aaa383dae7e5c056fbf10cf9855a9f79949f20ee225c04f06ddde56323600e0f3d6797e82d08d006e93761122527438ee9531620031c08c9e0d73cc 599 | languageName: node 600 | linkType: hard 601 | 602 | "source-map-js@npm:^1.0.2": 603 | version: 1.2.1 604 | resolution: "source-map-js@npm:1.2.1" 605 | checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3 606 | languageName: node 607 | linkType: hard 608 | 609 | "streamsearch@npm:^1.1.0": 610 | version: 1.1.0 611 | resolution: "streamsearch@npm:1.1.0" 612 | checksum: 10/612c2b2a7dbcc859f74597112f80a42cbe4d448d03da790d5b7b39673c1197dd3789e91cd67210353e58857395d32c1e955a9041c4e6d5bae723436b3ed9ed14 613 | languageName: node 614 | linkType: hard 615 | 616 | "styled-jsx@npm:5.1.6": 617 | version: 5.1.6 618 | resolution: "styled-jsx@npm:5.1.6" 619 | dependencies: 620 | client-only: "npm:0.0.1" 621 | peerDependencies: 622 | react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" 623 | peerDependenciesMeta: 624 | "@babel/core": 625 | optional: true 626 | babel-plugin-macros: 627 | optional: true 628 | checksum: 10/ba01200e8227fe1441a719c2e7da96c8aa7ef61d14211d1500e1abce12efa118479bcb6e7e12beecb9e1db76432caad2f4e01bbc0c9be21c134b088a4ca5ffe0 629 | languageName: node 630 | linkType: hard 631 | 632 | "tslib@npm:^2.4.0, tslib@npm:^2.8.0": 633 | version: 2.8.1 634 | resolution: "tslib@npm:2.8.1" 635 | checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 636 | languageName: node 637 | linkType: hard 638 | 639 | "typescript@npm:^5": 640 | version: 5.8.3 641 | resolution: "typescript@npm:5.8.3" 642 | bin: 643 | tsc: bin/tsc 644 | tsserver: bin/tsserver 645 | checksum: 10/65c40944c51b513b0172c6710ee62e951b70af6f75d5a5da745cb7fab132c09ae27ffdf7838996e3ed603bb015dadd099006658046941bd0ba30340cc563ae92 646 | languageName: node 647 | linkType: hard 648 | 649 | "typescript@patch:typescript@npm%3A^5#optional!builtin": 650 | version: 5.8.3 651 | resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=379a07" 652 | bin: 653 | tsc: bin/tsc 654 | tsserver: bin/tsserver 655 | checksum: 10/98470634034ec37fd9ea61cc82dcf9a27950d0117a4646146b767d085a2ec14b137aae9642a83d1c62732d7fdcdac19bb6288b0bb468a72f7a06ae4e1d2c72c9 656 | languageName: node 657 | linkType: hard 658 | 659 | "undici-types@npm:~6.19.2": 660 | version: 6.19.8 661 | resolution: "undici-types@npm:6.19.8" 662 | checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 663 | languageName: node 664 | linkType: hard 665 | -------------------------------------------------------------------------------- /tests/AllLoaders.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { render, screen } from "@testing-library/react"; 3 | import "@testing-library/jest-dom"; 4 | 5 | import * as Loaders from "../src"; 6 | import { LoaderHeightWidthRadiusProps, LoaderSizeMarginProps } from "../src/helpers/props"; 7 | 8 | Object.entries(Loaders).forEach((loader) => { 9 | const name = loader[0]; 10 | 11 | const Loader = loader[1] as React.ComponentType; 12 | 13 | describe(name, () => { 14 | it("should render nothing is loading prop is false", () => { 15 | const { container } = render(); 16 | expect(container.firstChild).toBeNull(); 17 | }); 18 | 19 | it("should have allow style override on wrapper", () => { 20 | const style = { overflow: "scroll" }; 21 | const { container } = render(); 22 | expect(container.firstChild).toHaveStyle(style); 23 | }); 24 | 25 | it("should have allow custom html props", () => { 26 | render(); 27 | expect(screen.queryByLabelText("aria-label")).toBeTruthy(); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es2015" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "sourceMap": false, 5 | "strictNullChecks": true, 6 | "strictFunctionTypes": true, 7 | "strictPropertyInitialization": true, 8 | "strictBindCallApply": true, 9 | "module": "es2015", 10 | "jsx": "react", 11 | "target": "es5", 12 | "moduleResolution": "node", 13 | "experimentalDecorators": true, 14 | "esModuleInterop": true, 15 | "declaration": true, 16 | "lib": ["dom", "es2017", "es5", "es6", "es7"], 17 | "outDir": ".", 18 | "strict": true, 19 | "noImplicitAny": true, 20 | "noImplicitThis": true, 21 | "noImplicitReturns": true, 22 | "skipLibCheck": true 23 | }, 24 | "exclude": [ 25 | "docs/*", 26 | "webpack.config.*", 27 | "*.js", 28 | "tests", 29 | "examples", 30 | "src/*.test.tsx", 31 | "src/helpers/*.test.*", 32 | "stories", 33 | "test-apps" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /vite.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | root: "examples", 8 | build: { 9 | outDir: "../dist", 10 | emptyOutDir: true, 11 | }, 12 | base: "react-spinners", 13 | }); 14 | --------------------------------------------------------------------------------