├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .lintstagedrc ├── .prettierrc ├── .snyk ├── .travis.yml ├── LICENSE.md ├── README.md ├── babel.config.js ├── codecov.yml ├── example ├── .babelrc ├── .gitignore ├── babel.config.js ├── jest.config.js ├── package.json ├── src │ ├── components │ │ ├── code.js │ │ ├── colorPicker.js │ │ ├── form.js │ │ ├── index.js │ │ ├── layout.js │ │ └── loaderItem.js │ ├── main.js │ └── styles │ │ └── index.scss └── yarn.lock ├── greenkeeper.json ├── jest.config.js ├── package.json ├── public └── index.html ├── src ├── components │ ├── __tests__ │ │ └── barLoader.spec.js │ ├── barLoader.js │ ├── beatLoader.js │ ├── bounceLoader.js │ ├── circleLoader.js │ ├── climbingBoxLoader.js │ ├── clipLoader.js │ ├── dotLoader.js │ ├── fadeLoader.js │ ├── gridLoader.js │ ├── hashLoader.js │ ├── index.js │ ├── moonLoader.js │ ├── pacmanLoader.js │ ├── propagateLoader.js │ ├── pulseLoader.js │ ├── ringLoader.js │ ├── riseLoader.js │ ├── rotateLoader.js │ ├── scaleLoader.js │ ├── skewLoader.js │ ├── squareLoader.js │ └── syncLoader.js ├── index.js └── utils.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { join } = require(`path`) 2 | 3 | const srcDir = join(__dirname, `src`) 4 | 5 | module.exports = { 6 | root: true, 7 | 8 | env: { 9 | es6: true, 10 | node: true, 11 | browser: true, 12 | jest: true 13 | }, 14 | 15 | plugins: [ 16 | `import`, // https://github.com/benmosher/eslint-plugin-import 17 | `jest`, // https://github.com/jest-community/eslint-plugin-jest 18 | `promise`, // https://github.com/xjamundx/eslint-plugin-promise 19 | `vue` // https://github.com/vuejs/eslint-plugin-vue 20 | ], 21 | 22 | extends: [ 23 | `plugin:jest/recommended`, 24 | 'plugin:vue/essential', 25 | `plugin:vue/recommended` 26 | ], 27 | 28 | settings: { 29 | polyfills: [`fetch`, `Promise`] 30 | }, 31 | 32 | parser: `vue-eslint-parser`, 33 | 34 | parserOptions: { 35 | parser: 'babel-eslint', 36 | ecmaVersion: 2017, 37 | sourceType: 'module', 38 | ecmaFeatures: { 39 | arrowFunctions: true, 40 | blockBindings: true, 41 | classes: true, 42 | defaultParams: true, 43 | destructuring: true, 44 | forOf: true, 45 | generators: false, 46 | modules: true, 47 | objectLiteralComputedProperties: true, 48 | objectLiteralDuplicateProperties: false, 49 | objectLiteralShorthandMethods: true, 50 | objectLiteralShorthandProperties: true, 51 | spread: true, 52 | superInFunctions: true, 53 | templateStrings: true, 54 | jsx: true 55 | } 56 | }, 57 | 58 | rules: { 59 | 'for-direction': 2, 60 | 'no-await-in-loop' : 2, 61 | 'no-compare-neg-zero' : 2, 62 | 'no-cond-assign' : [2, 'always'], 63 | 'no-console' : process.env.NODE_ENV === 'production' ? 'error' : 'off', 64 | 'no-constant-condition' : 1, 65 | 'no-control-regex' : 2, 66 | 'no-debugger' : process.env.NODE_ENV === 'production' ? 'error' : 'off', 67 | 'no-dupe-args' : 2, 68 | 'no-dupe-keys' : 2, 69 | 'no-duplicate-case' : 2, 70 | 'no-empty' : 2, 71 | 'no-empty-character-class' : 2, 72 | 'no-ex-assign' : 2, 73 | 'no-extra-boolean-cast' : 2, 74 | 'no-extra-parens' : [2, 'functions'], 75 | 'no-extra-semi' : 2, 76 | 'no-func-assign' : 2, 77 | 'no-inner-declarations' : 2, 78 | 'no-invalid-regexp' : 2, 79 | 'no-irregular-whitespace' : 2, 80 | 'no-obj-calls' : 2, 81 | 'no-prototype-builtins' : 2, 82 | 'no-regex-spaces' : 2, 83 | 'no-sparse-arrays' : 2, 84 | 'no-template-curly-in-string' : 2, 85 | 'no-unexpected-multiline' : 0, 86 | 'no-unreachable' : 2, 87 | 'no-unsafe-finally' : 2, 88 | 'no-unsafe-negation' : 2, 89 | 'use-isnan' : 2, 90 | 'valid-jsdoc' : 0, 91 | 'valid-typeof' : 2, 92 | 93 | 'accessor-pairs': 2, 94 | 'array-callback-return' : 1, 95 | 'block-scoped-var' : 2, 96 | 'class-methods-use-this' : 0, 97 | complexity : [0, { max: 20 }], 98 | 'consistent-return' : 0, 99 | curly : [2, 'multi-line'], 100 | 'default-case' : 2, 101 | 'dot-location' : [2, 'property'], 102 | 'dot-notation' : [2, { allowKeywords: true }], 103 | eqeqeq : [2, 'smart'], 104 | 'guard-for-in' : 2, 105 | 'no-alert' : 2, 106 | 'no-caller' : 2, 107 | 'no-case-declarations' : 2, 108 | 'no-div-regex' : 2, 109 | 'no-else-return' : 2, 110 | 'no-empty-function' : [2, { allow: ['arrowFunctions', 'constructors'] }], 111 | 'no-empty-pattern' : 2, 112 | 'no-eq-null' : 2, 113 | 'no-eval' : 2, 114 | 'no-extend-native' : 2, 115 | 'no-extra-bind' : 2, 116 | 'no-extra-label' : 2, 117 | 'no-fallthrough' : 2, 118 | 'no-floating-decimal' : 2, 119 | 'no-global-assign' : 2, 120 | 'no-implicit-coercion' : 0, 121 | 'no-implicit-globals' : 0, 122 | 'no-implied-eval' : 2, 123 | 'no-invalid-this' : 0, 124 | 'no-iterator' : 2, 125 | 'no-labels' : 2, 126 | 'no-lone-blocks' : 2, 127 | 'no-loop-func' : 2, 128 | 'no-magic-numbers' : 0, 129 | 'no-multi-spaces' : [2, { exceptions: { Property: true, VariableDeclarator: true, ImportDeclaration: true } }], 130 | 'no-multi-str' : 2, 131 | 'no-new' : 2, 132 | 'no-new-func' : 2, 133 | 'no-new-wrappers' : 2, 134 | 'no-octal' : 2, 135 | 'no-octal-escape' : 2, 136 | 'no-param-reassign' : 2, 137 | 'no-proto' : 2, 138 | 'no-redeclare' : 2, 139 | 'no-restricted-properties' : 0, 140 | 'no-return-assign' : [2, 'always'], 141 | 'no-return-await' : 2, 142 | 'no-script-url' : 2, 143 | 'no-self-assign' : [2, { props: true }], 144 | 'no-self-compare' : 2, 145 | 'no-sequences' : 2, 146 | 'no-throw-literal' : 2, 147 | 'no-unmodified-loop-condition' : 2, 148 | 'no-unused-expressions' : 0, 149 | 'no-unused-labels' : 2, 150 | 'no-useless-call' : 2, 151 | 'no-useless-concat' : 2, 152 | 'no-useless-escape' : 2, 153 | 'no-useless-return' : 2, 154 | 'no-void' : 2, 155 | 'no-warning-comments' : 0, 156 | 'no-with' : 2, 157 | 'prefer-promise-reject-errors' : 2, 158 | radix : 2, 159 | 'require-await' : 2, 160 | 'vars-on-top' : 2, 161 | 'wrap-iife' : [2, 'any'], 162 | yoda : 2, 163 | 164 | strict: [ 165 | 2, 166 | 'never' 167 | ], 168 | 169 | 'init-declarations': 0, 170 | 'no-catch-shadow' : 0, 171 | 'no-delete-var' : 2, 172 | 'no-label-var' : 2, 173 | 'no-restricted-globals' : 0, 174 | 'no-shadow' : 2, 175 | 'no-shadow-restricted-names' : 2, 176 | 'no-undef' : 2, 177 | 'no-undef-init' : 2, 178 | 'no-undefined' : 2, 179 | 'no-unused-vars' : [2, { vars: 'local', args: 'none', argsIgnorePattern: '^_' }], 180 | 'no-use-before-define' : [2, { functions: true, classes: true }], 181 | 182 | 'callback-return': 0, 183 | 'global-require' : 0, 184 | 'handle-callback-err' : 2, 185 | 'no-buffer-constructor' : 2, 186 | 'no-mixed-requires' : 0, 187 | 'no-new-require' : 2, 188 | 'no-path-concat' : 0, 189 | 'no-process-env' : 0, 190 | 'no-process-exit' : 0, 191 | 'no-restricted-modules' : 0, 192 | 'no-sync' : 0, 193 | 194 | 'array-bracket-newline' : [2, { multiline: true }], 195 | 'array-bracket-spacing' : 2, 196 | 'array-element-newline' : 0, 197 | 'block-spacing' : [2, 'always'], 198 | 'brace-style' : [2, '1tbs', { allowSingleLine: true }], 199 | camelcase : [2, { properties: 'never' }], 200 | 'capitalized-comments' : 0, 201 | 'comma-dangle' : [2, 'never'], 202 | 'comma-spacing' : [2, { before: false, after: true }], 203 | 'comma-style' : [2, 'last'], 204 | 'computed-property-spacing' : 2, 205 | 'consistent-this' : 0, 206 | 'eol-last' : 2, 207 | 'func-call-spacing' : 2, 208 | 'func-name-matching' : 0, 209 | 'func-names' : 0, 210 | 'func-style' : [2, 'declaration', { allowArrowFunctions: true }], 211 | 'function-paren-newline' : [2, 'consistent'], 212 | 'id-blacklist' : 0, 213 | 'id-length' : 0, 214 | 'id-match' : 0, 215 | indent : [2, 2, { SwitchCase: 1 }], 216 | 'jsx-quotes' : [2, 'prefer-double'], 217 | 'key-spacing' : 0, 218 | 'keyword-spacing' : [2, { before: true, after: true }], 219 | 'line-comment-position' : 0, 220 | 'linebreak-style' : 0, 221 | 'lines-around-comment' : 0, 222 | 'lines-between-class-members' : [2, 'always', { exceptAfterSingleLine: true }], 223 | 'max-depth' : 0, 224 | 'max-len' : 0, 225 | 'max-lines' : 0, 226 | 'max-nested-callbacks' : 0, 227 | 'max-params' : 0, 228 | 'max-statements' : 0, 229 | 'max-statements-per-line' : 0, 230 | 'multiline-comment-style' : 0, 231 | 'multiline-ternary' : [2, 'always-multiline'], 232 | 'new-cap' : 0, 233 | 'new-parens' : 2, 234 | 'newline-per-chained-call' : 0, 235 | 'no-array-constructor' : 0, 236 | 'no-bitwise' : [2, { allow: ['~'] }], 237 | 'no-continue' : 2, 238 | 'no-inline-comments' : 0, 239 | 'no-lonely-if' : 2, 240 | 'no-mixed-operators' : 0, 241 | 'no-mixed-spaces-and-tabs' : [2, 'smart-tabs'], 242 | 'no-multi-assign' : 2, 243 | 'no-multiple-empty-lines' : [2, { max: 2 }], 244 | 'no-negated-condition' : 2, 245 | 'no-nested-ternary' : 0, 246 | 'no-new-object' : 2, 247 | 'no-plusplus' : 0, 248 | 'no-restricted-syntax' : 0, 249 | 'no-tabs' : 0, 250 | 'no-ternary' : 0, 251 | 'no-trailing-spaces' : 2, 252 | 'no-underscore-dangle' : 0, 253 | 'no-unneeded-ternary' : 2, 254 | 'no-whitespace-before-property' : 2, 255 | 'nonblock-statement-body-position' : 0, 256 | 'object-curly-newline' : [2, { ObjectExpression: { consistent: true }, ObjectPattern: { consistent: true } }], 257 | 'object-curly-spacing' : [2, 'always'], 258 | 'object-property-newline' : [2, { allowMultiplePropertiesPerLine: true }], 259 | 'one-var' : [2, 'never'], 260 | 'one-var-declaration-per-line' : [2, 'always'], 261 | 'operator-assignment' : [2, 'always'], 262 | 'operator-linebreak' : [2, 'before', { overrides: { '&&': 'ignore', '=': 'ignore' } }], 263 | 'padded-blocks' : [2, 'never'], 264 | 'padding-line-between-statements' : 0, 265 | 'quote-props' : [2, 'consistent-as-needed'], 266 | quotes : [2, 'backtick', 'avoid-escape'], 267 | 'require-jsdoc' : 0, 268 | semi : [2, 'never'], 269 | 'semi-spacing' : [2, { before: false, after: true }], 270 | 'semi-style' : [2, 'last'], 271 | 'sprt-keys' : 0, 272 | 'sort-vars' : 0, 273 | 'space-before-blocks' : 2, 274 | 'space-before-function-paren' : [2, { anonymous: 'never', named: 'never', asyncArrow: 'always' }], 275 | 'space-in-parens' : [2, 'never'], 276 | 'space-infix-ops' : 2, 277 | 'space-unary-ops' : [2, { words: true, nonwords: false }], 278 | 'spaced-comment' : [0, 'always', { plugins: ['react'], exceptions: ['*'], markers: ['*'] }], 279 | 'switch-colon-spacing' : [2, { before: false, after: true }], 280 | 'template-tag-spacing' : [2, 'never'], 281 | 'unicode-bom' : 0, 282 | 'wrap-regex' : 2, 283 | 284 | 'arrow-body-style': [ 285 | 2, 286 | 'as-needed' 287 | ], 288 | 'arrow-parens' : [2, 'as-needed'], 289 | 'arrow-spacing' : [2, { before: true, after: true }], 290 | 'constructor-super' : 0, 291 | 'generator-star-spacing' : [2, { before: true, after: false }], 292 | 'no-class-assign' : 2, 293 | 'no-confusing-arrow' : [2, { allowParens: true }], 294 | 'no-const-assign' : 2, 295 | 'no-dupe-class-members' : 2, 296 | 'no-duplicate-imports' : 2, 297 | 'no-new-symbol' : 2, 298 | 'no-restricted-imports' : 0, 299 | 'no-this-before-super' : 2, 300 | 'no-useless-computed-key' : 2, 301 | 'no-useless-constructor' : 2, 302 | 'no-useless-rename' : 2, 303 | 'no-var' : 2, 304 | 'object-shorthand' : [2, 'properties'], 305 | 'prefer-arrow-callback' : 2, 306 | 'prefer-const' : 0, 307 | 'prefer-destructuring' : 0, 308 | 'prefer-numeric-literals' : 2, 309 | 'prefer-rest-params' : 2, 310 | 'prefer-spread' : 2, 311 | 'prefer-template' : 2, 312 | 'require-yield' : 2, 313 | 'rest-spread-spacing' : [2, 'never'], 314 | 'sort-imports' : 0, 315 | 'symbol-description' : 2, 316 | 'template-curly-spacing' : [2, 'never'], 317 | 'yield-star-spacing' : [2, { before: false, after: true }], 318 | 319 | 'import/no-unresolved': [ 320 | 2, 321 | { 322 | commonjs: true, 323 | amd: true 324 | } 325 | ], 326 | 'import/named' : 2, 327 | 'import/default' : 2, 328 | 'import/no-webpack-loader-syntax' : 2, 329 | 'import/export' : 2, 330 | 'import/no-deprecated' : 2, 331 | 'import/no-mutable-exports' : 2, 332 | 'import/no-duplicates' : 2, 333 | 'import/no-namespace' : 0, 334 | 'import/newline-after-import' : 2, 335 | 'import/order' : [2, { 'newlines-between': 'never', groups: ['builtin', ['internal', 'external'], ['parent', 'sibling'], 'index'] }], 336 | 337 | 'promise/catch-or-return': [ 338 | 2, 339 | { 340 | terminationMethod: [ 341 | 'catch', 342 | 'finally' 343 | ] 344 | } 345 | ], 346 | 'promise/always-return' : 2, 347 | 'promise/no-return-wrap' : 2, 348 | 'promise/param-names' : 2, 349 | 'promise/prefer-await-to-then' : 2, 350 | 'promise/prefer-await-to-callbacks' : 2, 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | /lib/ 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | /test/unit/coverage/ 9 | /coverage 10 | 11 | # Editor directories and files 12 | .idea 13 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.{js,jsx}": [ 3 | "prettier --write", 4 | "eslint --fix", 5 | "git add" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false 10 | } -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.10.2 3 | ignore: {} 4 | patch: {} 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 8 5 | 6 | dist: trusty 7 | 8 | sudo: false 9 | 10 | cache: 11 | npm: true 12 | directories: 13 | - node_modules 14 | 15 | before_install: 16 | - yarn global add greenkeeper-lockfile@1 17 | 18 | before_script: 19 | - cd example && yarn && cd .. 20 | - greenkeeper-lockfile-update 21 | - yarn test:coverage 22 | 23 | script: 24 | - yarn build 25 | 26 | after_script: 27 | - greenkeeper-lockfile-upload 28 | 29 | branches: 30 | only: 31 | - master 32 | - /^greenkeeper/.*$/ 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Drake Costa drake@saeris.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Vue Spinners

2 |

3 | 4 | npm 5 | 6 | 7 | travis 8 | 9 | 10 | codecov 11 | 12 | 13 | Known Vulnerabilities 14 | 15 | 16 | Known Vulnerabilities 17 | 18 |

19 |

A Vue.js port of react-spinners.

20 | 21 | ## 📦 Installation 22 | 23 | ```bash 24 | npm install --save @saeris/vue-spinners 25 | # or 26 | yarn add @saeris/vue-spinners 27 | ``` 28 | 29 | ## 🔧 Usage 30 | 31 | There are a number of ways you can use this library! Here are a few examples: 32 | 33 | **[Vue Plugin](https://vuejs.org/v2/guide/plugins.html#Using-a-Plugin)** 34 | ```js 35 | import Vue from 'vue' 36 | import { VueSpinners } from '@saeris/vue-spinners' 37 | 38 | Vue.use(VueSpinners) 39 | 40 | // Each spinner can now be used in your templates anywhere in the app! 41 | ``` 42 | 43 | **[Local Component Registration](https://vuejs.org/v2/guide/components-registration.html#Local-Registration)** 44 | ```js 45 | import { BarLoader } from '@saeris/vue-spinners' 46 | 47 | export default { 48 | components: { 49 | BarLoader 50 | }, 51 | // ... 52 | } 53 | ``` 54 | 55 | **[JSX Component](https://vuejs.org/v2/guide/render-function.html#JSX)** 56 | 57 | ```js 58 | import { BarLoader } from '@saeris/vue-spinners' 59 | 60 | export default { 61 | data: () => ({ 62 | loading: true 63 | }), 64 | render() { 65 | return ( 66 |
67 | 74 |
75 | ) 76 | } 77 | } 78 | ``` 79 | 80 | **[Unpkg Import](https://vuejs.org/v2/cookbook/packaging-sfc-for-npm.html#What-does-my-packaged-component-look-like)** 81 | ```html 82 | 83 | 84 | 85 | 86 | 87 |
88 | 89 |
90 | 91 | 94 | ``` 95 | 96 | ## 📋 Available Loaders, PropTypes, and Default Values 97 | 98 | Common default props for all loaders: 99 | 100 | ```js 101 | loading: true 102 | color: '#000000' 103 | ``` 104 | 105 | For `size`, `height`, and `width` props, there are `sizeUnit`, `heightUnit`, and `widthUnit` prop that accepts `px`, `%`, or `em`. The default for the unit prop is `px`. 106 | 107 | Loader | size:int | height:int | width:int | radius:int | margin:str 108 | -----------------------:|:--------:|:----------:|:---------:|:----------:|:---------: 109 | BarLoader | | `4` | `100` | | 110 | BeatLoader | `15` | | | | `2px` 111 | BounceLoader | `60` | | | | 112 | CircleLoader | `50` | | | | 113 | ClimbingBoxLoader | `15` | | | | 114 | ClipLoader | `35` | | | | 115 | DotLoader | `60` | | | | `2px` 116 | FadeLoader | | `15` | `5` | `2` | `2px` 117 | GridLoader | `15` | | | | 118 | HashLoader | `50` | | | | `2px` 119 | MoonLoader | `60` | | | | `2px` 120 | PacmanLoader | `25` | | | | `2px` 121 | PropagateLoader | `15` | | | | 122 | PulseLoader | `15` | | | | `2px` 123 | RingLoader | `60` | | | | `2px` 124 | RiseLoader | `15` | | | | `2px` 125 | RotateLoader | `15` | | | | `2px` 126 | ScaleLoader | | `35` | `4` | `2` | `2px` 127 | SkewLoader | `20` | | | | 128 | SquareLoader | `50` | | | | 129 | SyncLoader | `15` | | | | `2px` 130 | 131 | ## 🏖️ Demo 132 | 133 | You can either visit the [live demo site](https://vue-spinners.saeris.io), clone this repo and run the demo locally using `yarn start` and opening your browser to http://localhost:8080, or you can just play with it inside of CodeSandbox [here](https://codesandbox.io/s/github/Saeris/vue-spinners/tree/master/example). 134 | 135 | ## ⚠️ Support Notice 136 | 137 | This code is released as-is. It was originally built for use with Vue 2.x and as-such it is now very out of date. I do not plan to make continued updates to this package, so if you find it useful then I would highly recommend that you create a fork or copy-and-paste code to suit your own needs. The version of `emotion` that is uses is now very out of date and *will* cause problems when used in a modern codebase alongside another version of `emotion`. I am also unable to provide support or answer questions due to my lack of knowledge of Vuejs. 138 | 139 | ## 📣 Acknowledgements 140 | 141 | This library is a Vue port of [react-spinners](https://github.com/davidhu2000/react-spinners) by [David Hu](https://github.com/davidhu2000), who's library is based on [Halogen](https://github.com/yuanyan/halogen). 142 | 143 | ## 🥂 License 144 | 145 | Released under the [MIT license](https://github.com/Saeris/vue-spinners/blob/master/LICENSE.md). 146 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | `@vue/app` 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | parsers: 3 | javascript: 4 | enable_partials: yes 5 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "browsers": [ 9 | "> 1%", 10 | "last 2 versions", 11 | "not ie <= 8" 12 | ] 13 | } 14 | } 15 | ], 16 | "stage-2" 17 | ], 18 | "plugins": [ 19 | "vue-jsx-sync", 20 | "jsx-vue-functional", 21 | "transform-class-properties", 22 | "syntax-flow", 23 | "transform-flow-strip-types", 24 | "transform-decorators-legacy", 25 | "transform-vue-jsx", 26 | "transform-runtime" 27 | ], 28 | "env": { 29 | "test": { 30 | "presets": [ 31 | "env", 32 | "stage-2" 33 | ], 34 | "plugins": [ 35 | "vue-jsx-sync", 36 | "jsx-vue-functional", 37 | "transform-class-properties", 38 | "syntax-flow", 39 | "transform-flow-strip-types", 40 | "transform-vue-jsx", 41 | "transform-es2015-modules-commonjs", 42 | "dynamic-import-node" 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | /lib/ 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | /test/unit/coverage/ 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [`@vue/app`] 3 | } 4 | -------------------------------------------------------------------------------- /example/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | `js`, 4 | `jsx`, 5 | `json`, 6 | `vue` 7 | ], 8 | transform: { 9 | '^.+\\.vue$': `vue-jest`, 10 | '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': `jest-transform-stub`, 11 | '^.+\\.jsx?$': `babel-jest` 12 | }, 13 | moduleNameMapper: { 14 | '^@/(.*)$': `/src/$1` 15 | }, 16 | snapshotSerializers: [`jest-serializer-vue`], 17 | testMatch: [`**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)`], 18 | testURL: `http://localhost/` 19 | } 20 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@saeris/vue-spinner-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@saeris/vue-spinners": "^1.0.5", 7 | "emotion": "9.2.12", 8 | "v-click-outside": "2.0.1", 9 | "vue": "^2.5.2", 10 | "vue-class-component": "6.3.1", 11 | "vue-color": "2.6.0", 12 | "vue-emotion": "0.4.2" 13 | }, 14 | "devDependencies": { 15 | "autoprefixer": "^9.1.5", 16 | "babel-core": "^6.22.1", 17 | "babel-eslint": "^10.0.1", 18 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 19 | "babel-jest": "^23.6.0", 20 | "babel-loader": "^8.0.4", 21 | "babel-plugin-jsx-vue-functional": "2.1.0", 22 | "babel-plugin-syntax-flow": "6.18.0", 23 | "babel-plugin-syntax-jsx": "^6.18.0", 24 | "babel-plugin-transform-class-properties": "6.24.1", 25 | "babel-plugin-transform-decorators-legacy": "1.3.5", 26 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 27 | "babel-plugin-transform-flow-strip-types": "6.22.0", 28 | "babel-plugin-transform-runtime": "^6.22.0", 29 | "babel-plugin-transform-vue-jsx": "^3.5.0", 30 | "babel-plugin-vue-jsx-sync": "0.0.5", 31 | "babel-preset-env": "^1.3.2", 32 | "babel-preset-stage-2": "^6.22.0", 33 | "chalk": "^2.0.1", 34 | "copy-webpack-plugin": "^4.0.1", 35 | "css-loader": "^1.0.0", 36 | "eslint": "^5.6.1", 37 | "eslint-config-airbnb-base": "^13.1.0", 38 | "eslint-friendly-formatter": "^4.0.1", 39 | "eslint-import-resolver-webpack": "^0.10.1", 40 | "eslint-loader": "^2.1.1", 41 | "eslint-plugin-import": "^2.7.0", 42 | "eslint-plugin-vue": "^4.0.0", 43 | "extract-text-webpack-plugin": "^3.0.0", 44 | "file-loader": "^2.0.0", 45 | "flow-bin": "0.82.0", 46 | "friendly-errors-webpack-plugin": "^1.6.1", 47 | "html-webpack-plugin": "^3.2.0", 48 | "jest": "^23.6.0", 49 | "jest-serializer-vue": "^2.0.2", 50 | "node-notifier": "^5.1.2", 51 | "optimize-css-assets-webpack-plugin": "^5.0.1", 52 | "ora": "^3.0.0", 53 | "portfinder": "^1.0.13", 54 | "postcss-import": "^12.0.0", 55 | "postcss-loader": "^3.0.0", 56 | "postcss-url": "^8.0.0", 57 | "rimraf": "^2.6.0", 58 | "semver": "^5.3.0", 59 | "shelljs": "^0.8.2", 60 | "uglifyjs-webpack-plugin": "^2.0.1", 61 | "url-loader": "^1.1.1", 62 | "vue-jest": "^2.6.0", 63 | "vue-loader": "^15.4.2", 64 | "vue-style-loader": "^4.1.2", 65 | "vue-template-compiler": "^2.5.2", 66 | "webpack": "^4.20.2", 67 | "webpack-bundle-analyzer": "^3.0.2", 68 | "webpack-dev-server": "^3.1.9", 69 | "webpack-merge": "^4.1.0" 70 | }, 71 | "browserslist": [ 72 | "> 1%", 73 | "last 2 versions", 74 | "not ie <= 8" 75 | ], 76 | "lint-staged": { 77 | "*.js": [ 78 | "vue-cli-service lint", 79 | "git add" 80 | ], 81 | "*.vue": [ 82 | "vue-cli-service lint", 83 | "git add" 84 | ] 85 | }, 86 | "repository": { 87 | "type": "git", 88 | "url": "https://github.com/Saeris/vue-spinners" 89 | }, 90 | "description": "Vue port of React Spinners", 91 | "license": "MIT", 92 | "engines": { 93 | "node": ">= 6.0.0", 94 | "npm": ">= 3.0.0" 95 | }, 96 | "files": [ 97 | "dist/*", 98 | "src/*", 99 | "*.json", 100 | "*.js" 101 | ], 102 | "keywords": [ 103 | "loading spinners", 104 | "loaders", 105 | "spinners", 106 | "loading indicators" 107 | ], 108 | "gitHooks": { 109 | "pre-commit": "lint-staged" 110 | }, 111 | "main": "src/main.js" 112 | } 113 | -------------------------------------------------------------------------------- /example/src/components/code.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from 'vue-class-component' 3 | import styled from "vue-emotion" 4 | 5 | const Container = styled(`section`)` 6 | margin: 50px auto; 7 | color: #fff; 8 | display: -webkit-box; 9 | display: -ms-flexbox; 10 | display: flex; 11 | align-items: center; 12 | cursor: pointer; 13 | 14 | span { 15 | background: #2b303b; 16 | padding: 16px 32px; 17 | font-size: 20px; 18 | margin: 0 auto; 19 | font-family: Courier New, Courier, monospace; 20 | opacity: 0.9; 21 | transition: 0.3s ease-out; 22 | 23 | &:hover { 24 | box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 25 | 0 1px 7px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -1px rgba(0, 0, 0, 0.2); 26 | opacity: 1; 27 | } 28 | } 29 | ` 30 | 31 | @Component 32 | export class Code extends Vue { 33 | text = [ 34 | `yarn add @saeris/vue-spinners`, 35 | `npm install @saeris/vue-spinners --save` 36 | ] 37 | 38 | index = 0 39 | 40 | handleClick(e) { 41 | e.preventDefault() 42 | this.index = +!this.index 43 | } 44 | 45 | render() { 46 | return ( 47 | 48 | {this.text[this.index]} 49 | 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/src/components/colorPicker.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from 'vue-class-component' 3 | import styled from "vue-emotion" 4 | import { Sketch } from 'vue-color' 5 | import vClickOutside from 'v-click-outside' 6 | 7 | Vue.use(vClickOutside) 8 | 9 | const Container = styled(`section`)` 10 | top: ${({ pos }) => `${pos}px`}; 11 | left: 20px; 12 | position: fixed; 13 | z-index: 999; 14 | ` 15 | 16 | const Button = styled(`button`)` 17 | height: 36px; 18 | font-size: 14px; 19 | line-height: 36px; 20 | background: #2b303b; 21 | color: #fff; 22 | border: none; 23 | opacity: 0.9; 24 | -webkit-transition: 0.5s ease-in-out; 25 | -o-transition: 0.5s ease-in-out; 26 | transition: 0.5s ease-in-out; 27 | font-weight: 300; 28 | text-transform: uppercase; 29 | cursor: pointer; 30 | border-radius: 2px; 31 | letter-spacing: 0.5px; 32 | padding: 0 28px; 33 | 34 | &:hover { 35 | box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 36 | 0 1px 7px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -1px rgba(0, 0, 0, 0.2); 37 | opacity: 1; 38 | } 39 | ` 40 | 41 | @Component({ 42 | props: { 43 | color: { 44 | type: String, 45 | required: true 46 | }, 47 | updateColor: { 48 | type: Function, 49 | required: true 50 | } 51 | } 52 | }) 53 | export class ColorPicker extends Vue { 54 | pos = 370 55 | showPicker = false 56 | 57 | beforeMount() { 58 | window.addEventListener(`scroll`, this.updatePosition, true) 59 | } 60 | 61 | beforeDestroy() { 62 | window.removeEventListener(`scorll`, this.updatePosition, true) 63 | } 64 | 65 | updatePosition() { 66 | let top = 370 - (window.scrollY * 2) 67 | if (top > 60) { 68 | this.pos = top 69 | } else { 70 | this.pos = 50 71 | } 72 | } 73 | 74 | togglePicker() { 75 | this.showPicker = !this.showPicker 76 | } 77 | 78 | render() { 79 | return ( 80 | 81 | {this.showPicker ? ( 82 | 87 | ) : ( 88 | 89 | )} 90 | 91 | ) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /example/src/components/form.js: -------------------------------------------------------------------------------- 1 | import styled from "vue-emotion" 2 | 3 | const Container = styled(`div`)` 4 | position: absolute; 5 | bottom: 5px; 6 | font-family: sans-serif, serif; 7 | width: 80%; 8 | display: -webkit-box; 9 | display: -ms-flexbox; 10 | display: flex; 11 | flex-direction: row; 12 | flex-wrap: wrap; 13 | justify-content: center; 14 | ` 15 | 16 | const InputGroup = styled(`div`)` 17 | margin: 3px; 18 | display: flex; 19 | width: 50px; 20 | height: 20px; 21 | position: relative; 22 | ` 23 | 24 | const Input = styled(`input`)` 25 | width: 50px; 26 | padding: 0 4px; 27 | display: block; 28 | border: none; 29 | border-bottom: 1px solid #757575; 30 | text-align: center; 31 | 32 | &:focus { 33 | outline: none; 34 | } 35 | ` 36 | 37 | const Bar = styled(`span`)` 38 | position: absolute; 39 | display: block; 40 | width: 100%; 41 | bottom: -1px; 42 | 43 | &::before { 44 | left: 50%; 45 | } 46 | 47 | &::after { 48 | right: 50%; 49 | } 50 | 51 | &::before, 52 | &::after { 53 | content: ''; 54 | height: 2px; 55 | width: 0; 56 | bottom: 1px; 57 | position: absolute; 58 | background: #36d7b7; 59 | transition: 0.2s ease all; 60 | } 61 | ` 62 | 63 | const Label = styled(`label`)` 64 | top: -20px; 65 | flex: 1; 66 | font-size: 10px; 67 | line-height: 20px; 68 | text-align: center; 69 | color: #999; 70 | position: absolute; 71 | pointer-events: none; 72 | transition: 0.2s ease all; 73 | width: 50px; 74 | ` 75 | 76 | export const Form = { 77 | functional: true, 78 | props: { 79 | inputs: { type: Object, default: () => ({}) }, 80 | update: { type: Function, required: true } 81 | }, 82 | render(h, { props: { inputs, update } }) { 83 | return ( 84 | 85 | {Object.keys(inputs).map(name => ( 86 | 87 | 93 | 94 | 95 | 96 | ))} 97 | 98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /example/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { Code } from './code' 2 | export { ColorPicker } from './colorPicker' 3 | export { Layout } from './layout' 4 | export { LoaderItem } from './loaderItem' 5 | -------------------------------------------------------------------------------- /example/src/components/layout.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from 'vue-class-component' 3 | import styled from 'vue-emotion' 4 | 5 | const Main = styled(`main`)` 6 | .spinners { 7 | margin-top: ${({ isFixed }) => `${isFixed ? 250 : 0}px`} 8 | } 9 | ` 10 | 11 | const Header = styled(`header`)` 12 | position: realtive; 13 | display: flex; 14 | align-items: center; 15 | width: 100%; 16 | height: ${({ height }) => `${height}px`}; 17 | color: #fff; 18 | z-index: 1000; 19 | background: linear-gradient(90deg, ${({ color }) => color}, #2b303b); 20 | 21 | &.fixed { 22 | position: fixed; 23 | top: 0; 24 | box-shadow: rgba(0, 0, 0, 0.14) 0px 3px 3px 0px, 25 | rgba(0, 0, 0, 0.12) 0px 1px 7px 0px, rgba(0, 0, 0, 0.2) 0px 3px 1px -1px; 26 | } 27 | ` 28 | 29 | const Logo = styled(`h1`)` 30 | height: 50px; 31 | line-height: 50px; 32 | display: block; 33 | text-align: center; 34 | margin: 0px auto; 35 | font-size: 40px; 36 | letter-spacing: 5px; 37 | font-weight: 300; 38 | text-transform: uppercase; 39 | position: -webkit-sticky; 40 | position: sticky; 41 | top: 10px; 42 | transition: 0.2s ease-in-out; 43 | 44 | &.fixed { 45 | font-size: 20px; 46 | } 47 | ` 48 | 49 | const Footer = styled(`footer`)` 50 | padding: 30px; 51 | margin: 15px 0 0 0; 52 | background: #2b303b; 53 | text-align: center; 54 | 55 | a { 56 | text-decoration: none; 57 | color: #ccc; 58 | } 59 | ` 60 | 61 | const Ribbon = styled(`img`)` 62 | position: absolute; 63 | top: 0; 64 | right: 0; 65 | border: 0; 66 | ` 67 | @Component({ 68 | props: { 69 | color: { 70 | type: String, 71 | required: true 72 | } 73 | } 74 | }) 75 | export class Layout extends Vue { 76 | isFixed = false 77 | height = 360 78 | 79 | beforeMount() { 80 | window.addEventListener(`scroll`, this.handleScroll, true) 81 | } 82 | 83 | beforeDestroy() { 84 | window.removeEventListener(`scorll`, this.handleScroll, true) 85 | } 86 | 87 | handleScroll() { 88 | let height = 360 - window.scrollY 89 | if (height > 200) { 90 | this.height = height 91 | this.isFixed = false 92 | } else { 93 | this.height = 40 94 | this.isFixed = true 95 | } 96 | } 97 | 98 | render() { 99 | return ( 100 |
101 |
102 | {this.isFixed 103 | ? null 104 | : ( 105 | 106 | 110 | 111 | )} 112 | Vue Spinners 113 |
114 | {this.$slots.default} 115 | 118 |
119 | ) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /example/src/components/loaderItem.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from 'vue-class-component' 3 | import styled from "vue-emotion" 4 | import { Form } from './form' 5 | 6 | const Item = styled(`li`)` 7 | flex: 0 0 auto; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: center; 12 | padding: 20px; 13 | border: 0.5px solid #e2e2e2; 14 | box-shadow: 1px 1px 1px #e2e2e2; 15 | width: 300px; 16 | height: 300px; 17 | margin: 30px; 18 | font-size: 18px; 19 | letter-spacing: 1px; 20 | position: relative; 21 | ` 22 | 23 | const Title = styled(`span`)` 24 | position: absolute; 25 | top: 20px; 26 | ` 27 | 28 | @Component({ 29 | props: { 30 | spinner: { 31 | type: Object, 32 | required: true 33 | }, 34 | name: { 35 | type: String, 36 | required: true 37 | }, 38 | color: { 39 | type: String, 40 | required: true 41 | } 42 | } 43 | }) 44 | export class LoaderItem extends Vue { 45 | defaults = {} 46 | 47 | beforeMount() { 48 | const props = Object.entries(this.spinner.props).filter( 49 | ([key, values]) => values.default 50 | ) 51 | const defaults = props.reduce((hash, [key, values]) => { 52 | hash[key] = values.default 53 | return hash 54 | }, {}) 55 | const propTypes = props.reduce((hash, [key, values]) => { 56 | hash[key] = values.type 57 | return hash 58 | }, {}) 59 | 60 | delete defaults.color 61 | delete defaults.loading 62 | delete defaults.sizeUnit 63 | delete defaults.widthUnit 64 | delete defaults.heightUnit 65 | delete defaults.radiusUnit 66 | delete defaults.loaderStyle 67 | 68 | this.defaults = defaults 69 | this.propTypes = propTypes 70 | } 71 | 72 | update(field) { 73 | return e => { 74 | this.defaults[field] = this.propTypes[field](e.target.value) 75 | } 76 | } 77 | 78 | renderSpinner(Spinner) { 79 | return ( 80 | 88 | ) 89 | } 90 | 91 | render() { 92 | return ( 93 | 94 | {this.name} 95 | {this.renderSpinner(this.spinner)} 96 |
97 | 98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /example/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import Component from "vue-class-component" 3 | import Components from "@saeris/vue-spinners" 4 | import styled from "vue-emotion" 5 | import { Code, ColorPicker, Layout, LoaderItem } from "./components" 6 | import "./styles/index.scss" 7 | 8 | const Spinners = styled(`section`)` 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | min-width: 300px; 13 | width: 80%; 14 | margin: 0 auto; 15 | ` 16 | 17 | const List = styled(`ul`)` 18 | display: flex; 19 | flex-wrap: wrap; 20 | justify-content: space-around; 21 | background: #fff; 22 | ` 23 | 24 | @Component 25 | class App extends Vue { 26 | color = `#36D7B7` 27 | 28 | updateColor(color) { 29 | this.color = color.hex 30 | } 31 | 32 | renderSpinner(Spinner) { 33 | return 34 | } 35 | 36 | render() { 37 | return ( 38 | 39 | 40 | 41 | 42 | 43 | {Object.entries(Components).map(([name, spinner]) => ( 44 | 45 | ))} 46 | 47 | 48 | 49 | ) 50 | } 51 | } 52 | 53 | // eslint-disable-next-line 54 | new Vue({ 55 | el: `#app`, 56 | render: h => h(App) 57 | }) 58 | -------------------------------------------------------------------------------- /example/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | textarea, 6 | input, 7 | button { 8 | outline: none; 9 | } 10 | 11 | input:-webkit-autofill { 12 | box-shadow: 0 0 0 30px white inset; 13 | } 14 | 15 | html, 16 | body { 17 | padding: 0; 18 | margin: 0; 19 | font-family: 'Roboto', sans-serif; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | background: #e2e2e2; 23 | 24 | button { 25 | border: none; 26 | background: none; 27 | 28 | &:active { 29 | outline: none; 30 | } 31 | 32 | &:focus { 33 | outline: none; 34 | } 35 | } 36 | 37 | ul { 38 | padding: 0; 39 | margin: 0; 40 | list-style: none; 41 | 42 | li { 43 | width: auto; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "default": { 4 | "packages": [ 5 | "example/package.json", 6 | "package.json" 7 | ] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | moduleNameMapper: { 14 | '^@/(.*)$': '/src/$1' 15 | }, 16 | snapshotSerializers: [ 17 | 'jest-serializer-vue' 18 | ], 19 | testMatch: [ 20 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 21 | ], 22 | testURL: 'http://localhost/' 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@saeris/vue-spinners", 3 | "version": "1.0.8", 4 | "description": "Vue port of React Spinners", 5 | "license": "MIT", 6 | "keywords": [ 7 | "loading spinners", 8 | "loaders", 9 | "spinners", 10 | "loading indicators" 11 | ], 12 | "main": "./lib/@saeris/vue-spinners.common.js", 13 | "unpkg": "./lib/@saeris/vue-spinners.umd.min.js", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/Saeris/vue-spinners" 17 | }, 18 | "engines": { 19 | "node": ">= 6.0.0", 20 | "npm": ">= 3.0.0" 21 | }, 22 | "scripts": { 23 | "start": "vue-cli-service serve ./example/src/main.js", 24 | "build": "yarn build:demo && yarn build:lib", 25 | "build:demo": "vue-cli-service build --dest ./example/dist --modern --target app --name demo ./example/src/main.js", 26 | "build:lib": "vue-cli-service build --dest lib --target lib ./src/index.js", 27 | "lint": "vue-cli-service lint", 28 | "test": "yarn lint && vue-cli-service test:unit", 29 | "test:watch": "yarn test --watch", 30 | "test:coverage": "yarn test --coverage && codecov", 31 | "precommit": "lint-staged", 32 | "prepublish": "snyk protect", 33 | "prepublishOnly": "yarn test && yarn build:lib", 34 | "release": "yarn publish --access public" 35 | }, 36 | "peerDependencies": { 37 | "emotion": "9.2.6", 38 | "vue": "^2.5.2", 39 | "vue-emotion": "0.4.2" 40 | }, 41 | "devDependencies": { 42 | "@vue/cli": "^3.0.1", 43 | "@vue/cli-plugin-babel": "^3.0.1", 44 | "@vue/cli-plugin-eslint": "^3.0.1", 45 | "@vue/cli-plugin-unit-jest": "^3.0.1", 46 | "@vue/cli-service": "^3.0.1", 47 | "@vue/test-utils": "^1.0.0-beta.20", 48 | "babel-core": "7.0.0-bridge.0", 49 | "babel-jest": "^23.6.0", 50 | "codecov": "^3.1.0", 51 | "emotion": "9.2.12", 52 | "eslint": "^5.6.1", 53 | "eslint-friendly-formatter": "^4.0.1", 54 | "eslint-import-resolver-webpack": "^0.10.1", 55 | "eslint-loader": "^2.1.1", 56 | "eslint-plugin-import": "^2.7.0", 57 | "eslint-plugin-jest": "^21.21.0", 58 | "eslint-plugin-promise": "^4.0.0", 59 | "eslint-plugin-vue": "^4.7.1", 60 | "jest": "^23.6.0", 61 | "lint-staged": "^7.2.2", 62 | "node-sass": "^4.9.3", 63 | "sass-loader": "^7.1.0", 64 | "snyk": "^1.100.1", 65 | "vue": "^2.5.2", 66 | "vue-emotion": "0.4.2", 67 | "vue-template-compiler": "^2.5.17" 68 | }, 69 | "browserslist": [ 70 | "> 1%", 71 | "last 2 versions", 72 | "not ie <= 8" 73 | ], 74 | "files": [ 75 | "lib/*", 76 | "src/*", 77 | "*.json", 78 | "*.js" 79 | ], 80 | "gitHooks": { 81 | "pre-commit": "lint-staged" 82 | }, 83 | "lint-staged": { 84 | "*.js": [ 85 | "vue-cli-service lint", 86 | "git add" 87 | ], 88 | "*.vue": [ 89 | "vue-cli-service lint", 90 | "git add" 91 | ] 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> 7 | 8 | 9 | Vue Spinners 10 | 11 | <% if (htmlWebpackPlugin.files.favicon) { %> 12 | 13 | <% } %> 14 | 15 | 16 | <% for (var css in htmlWebpackPlugin.files.css) { %> 17 | 18 | <% } %> 19 | 20 | 21 |
22 | 23 | <% if (htmlWebpackPlugin.options.window) { %> 24 | 29 | <% } %> 30 | 31 | <% for (var chunk in htmlWebpackPlugin.files.chunks) { %> 32 | 33 | <% } %> 34 | 35 | <% if (htmlWebpackPlugin.options.devServer) { %> 36 | 37 | <% } %> 38 | 39 | <% if (htmlWebpackPlugin.options.googleAnalytics) { %> 40 | 52 | <% } %> 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/components/__tests__/barLoader.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import { BarLoader } from '../barLoader.js' 3 | 4 | describe(`Bar Loader`, () => { 5 | it(`renders props.color when passed`, () => { 6 | const color = `#bada55` 7 | const wrapper = mount(BarLoader, { 8 | context: { 9 | props: { color } 10 | } 11 | }) 12 | expect(wrapper.attributes().color).toBe(color) 13 | }) 14 | 15 | it(`renders a div`, () => { 16 | const wrapper = mount(BarLoader) 17 | expect(wrapper.contains(`div`)).toBe(true) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/components/barLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { calculateRgba, range } from '../utils' 3 | 4 | const long = keyframes` 5 | 0% {left: -35%;right: 100%} 6 | 60% {left: 100%;right: -90%} 7 | 100% {left: 100%;right: -90%} 8 | ` 9 | 10 | const short = keyframes` 11 | 0% {left: -200%;right: 100%} 12 | 60% {left: 107%;right: -8%} 13 | 100% {left: 107%;right: -8%} 14 | ` 15 | 16 | const Wrapper = styled(`div`)` 17 | position: relative; 18 | width: ${({ width, widthUnit }) => `${width}${widthUnit}`}; 19 | height: ${({ height, heightUnit }) => `${height}${heightUnit}`}; 20 | overflow: hidden; 21 | background-color: ${({ color }) => calculateRgba(color, 0.2)}; 22 | background-clip: padding-box; 23 | ` 24 | 25 | const Bar = styled(`div`)` 26 | position: absolute; 27 | height: ${({ height, heightUnit }) => `${height}${heightUnit}`}; 28 | overflow: hidden; 29 | background-color: ${({ color }) => color}; 30 | background-clip: padding-box; 31 | display: block; 32 | border-radius: 2px; 33 | will-change: left, right; 34 | animation-fill-mode: forwards; 35 | animation: ${({ version }) => ` ${(version === 1 ? long : short)} 2.1s ${(version === 2 ? `1.15s` : ``)} ${(version === 1 ? `cubic-bezier(0.65, 0.815, 0.735, 0.395)` : `cubic-bezier(0.165, 0.84, 0.44, 1)`)} infinite`}; 36 | ` 37 | 38 | export const BarLoader = { 39 | functional: true, 40 | props: { 41 | loading: { type: Boolean, default: true }, 42 | color: { type: String, default: `#000000` }, 43 | width: { type: Number, default: 100 }, 44 | widthUnit: { type: String, default: `px` }, 45 | height: { type: Number, default: 4 }, 46 | heightUnit: { type: String, default: `px` } 47 | }, 48 | render(h, { props, data }) { 49 | return props.loading ? ( 50 | 58 | {range(2, 1).map(i => ( 59 | 65 | ))} 66 | 67 | ) : null 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/beatLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const beat = keyframes` 5 | 50% {transform: scale(0.75);opacity: 0.2} 6 | 100% {transform: scale(1);opacity: 1} 7 | ` 8 | 9 | const Circle = styled(`div`)` 10 | display: inline-block; 11 | background-color: ${({ color }) => color}; 12 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 13 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 14 | margin: ${({ margin }) => margin}; 15 | border-radius: 100%; 16 | animation: ${({ version }) => `${beat} 0.7s ${(version % 2 ? `0s` : `0.35s`)} infinite linear`}; 17 | animation-fill-mode: both; 18 | ` 19 | 20 | export const BeatLoader = { 21 | functional: true, 22 | props: { 23 | loading: { type: Boolean, default: true }, 24 | color: { type: String, default: `#000000` }, 25 | size: { type: Number, default: 15 }, 26 | sizeUnit: { type: String, default: `px` }, 27 | margin: { type: String, default: `2px` } 28 | }, 29 | render(h, { props, data }) { 30 | return props.loading ? ( 31 |
32 | {range(3, 1).map(i => ( 33 | 40 | ))} 41 |
42 | ) : null 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/bounceLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const bounce = keyframes` 5 | 0%, 100% {transform: scale(0)} 6 | 50% {transform: scale(1.0)} 7 | ` 8 | 9 | const Wrapper = styled(`div`)` 10 | position: relative; 11 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 12 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 13 | ` 14 | 15 | const Circle = styled(`div`)` 16 | position: absolute; 17 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 18 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 19 | background-color: ${({ color }) => color}; 20 | border-radius: 100%; 21 | opacity: 0.6; 22 | top: 0; 23 | left: 0; 24 | animation-fill-mode: both; 25 | animation: ${({ version }) => `${bounce} 2.1s ${(version === 1 ? `1s` : `0s`)} infinite ease-in-out`}; 26 | ` 27 | 28 | export const BounceLoader = { 29 | functional: true, 30 | props: { 31 | loading: { type: Boolean, default: true }, 32 | color: { type: String, default: `#000000` }, 33 | size: { type: Number, default: 60 }, 34 | sizeUnit: { type: String, default: `px` } 35 | }, 36 | render(h, { props, data }) { 37 | return props.loading ? ( 38 | 43 | {range(2, 1).map(i => ( 44 | 50 | ))} 51 | 52 | ) : null 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/circleLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const circle = keyframes` 5 | 0% {transform: rotate(0deg)} 6 | 50% {transform: rotate(180deg)} 7 | 100% {transform: rotate(360deg)} 8 | ` 9 | 10 | const Wrapper = styled(`div`)` 11 | position: relative; 12 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 13 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 14 | ` 15 | 16 | const Ring = styled(`div`)` 17 | position: absolute; 18 | height: ${({ size, sizeUnit, version }) => `${size * (1 - version / 10)}${sizeUnit}`}; 19 | width: ${({ size, sizeUnit, version }) => `${size * (1 - version / 10)}${sizeUnit}`}; 20 | border: ${({ color }) => `1px solid ${color}`}; 21 | border-radius: 100%; 22 | transition: 2s; 23 | border-bottom: none; 24 | border-right: none; 25 | top: ${({ version }) => version * 0.7 * 2.5}%; 26 | left: ${({ version }) => version * 0.35 * 2.5}%; 27 | animation-fill-mode: ''; 28 | animation: ${({ version }) => `${circle} 1s ${version * 0.2}s infinite linear`}; 29 | ` 30 | 31 | export const CircleLoader = { 32 | functional: true, 33 | props: { 34 | loading: { type: Boolean, default: true }, 35 | color: { type: String, default: `#000000` }, 36 | size: { type: Number, default: 50 }, 37 | sizeUnit: { type: String, default: `px` } 38 | }, 39 | render(h, { props, data }) { 40 | return props.loading ? ( 41 | 46 | {range(5).map(i => ( 47 | 53 | ))} 54 | 55 | ) : null 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/climbingBoxLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | 3 | const climbingBox = keyframes` 4 | 0% {transform:translate(0, -1em) rotate(-45deg)} 5 | 5% {transform:translate(0, -1em) rotate(-50deg)} 6 | 20% {transform:translate(1em, -2em) rotate(47deg)} 7 | 25% {transform:translate(1em, -2em) rotate(45deg)} 8 | 30% {transform:translate(1em, -2em) rotate(40deg)} 9 | 45% {transform:translate(2em, -3em) rotate(137deg)} 10 | 50% {transform:translate(2em, -3em) rotate(135deg)} 11 | 55% {transform:translate(2em, -3em) rotate(130deg)} 12 | 70% {transform:translate(3em, -4em) rotate(217deg)} 13 | 75% {transform:translate(3em, -4em) rotate(220deg)} 14 | 100% {transform:translate(0, -1em) rotate(-225deg)} 15 | ` 16 | 17 | const Container = styled(`div`)` 18 | position: relative; 19 | width: 7.1em; 20 | height: 7.1em; 21 | ` 22 | 23 | const Wrapper = styled(`div`)` 24 | position: absolute; 25 | top: 50%; 26 | left: 50%; 27 | margin-top: -2.7em; 28 | margin-left: -2.7em; 29 | width: 5.4em; 30 | height: 5.4em; 31 | font-size: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 32 | ` 33 | 34 | const Box = styled(`div`)` 35 | position: absolute; 36 | left: 0; 37 | bottom: -0.1em; 38 | height: 1em; 39 | width: 1em; 40 | background-color: transparent; 41 | border-radius: 15%; 42 | border: ${({ color }) => `0.25em solid ${color}`}; 43 | transform: translate(0, -1em) rotate(-45deg); 44 | animation-fill-mode: both; 45 | animation: ${climbingBox} 2.5s infinite cubic-bezier(0.79, 0, 0.47, 0.97); 46 | ` 47 | 48 | const Hill = styled(`div`)` 49 | position: absolute; 50 | width: 7.1em; 51 | height: 7.1em; 52 | top: 1.7em; 53 | left: 1.7em; 54 | border-left: ${({ color }) => `0.25em solid ${color}`}; 55 | transform: rotate(45deg); 56 | ` 57 | 58 | export const ClimbingBoxLoader = { 59 | functional: true, 60 | props: { 61 | loading: { type: Boolean, default: true }, 62 | color: { type: String, default: `#000000` }, 63 | size: { type: Number, default: 15 }, 64 | sizeUnit: { type: String, default: `px` } 65 | }, 66 | render(h, { props, data }) { 67 | return props.loading ? ( 68 | 69 | 74 | 75 | 76 | 77 | 78 | ) : null 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/clipLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | 3 | const clip = keyframes` 4 | 0% {transform: rotate(0deg) scale(1)} 5 | 50% {transform: rotate(180deg) scale(0.8)} 6 | 100% {transform: rotate(360deg) scale(1)} 7 | ` 8 | 9 | const Ring = styled(`div`)` 10 | background: transparent !important; 11 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 12 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 13 | border-radius: 100%; 14 | border: 2px solid; 15 | border-color: ${({ color }) => color}; 16 | border-bottom-color: transparent; 17 | display: inline-block; 18 | animation: ${clip} 0.75s 0s infinite linear; 19 | animation-fill-mode: both; 20 | ` 21 | 22 | export const ClipLoader = { 23 | functional: true, 24 | props: { 25 | loading: { type: Boolean, default: true }, 26 | color: { type: String, default: `#000000` }, 27 | size: { type: Number, default: 35 }, 28 | sizeUnit: { type: String, default: `px` } 29 | }, 30 | render(h, { props, data }) { 31 | return props.loading ? ( 32 | 38 | ) : null 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/dotLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const rotate = keyframes` 5 | 100% {transform: rotate(360deg)} 6 | ` 7 | 8 | const bounce = keyframes` 9 | 0%, 100% {transform: scale(0)} 10 | 50% {transform: scale(1.0)} 11 | ` 12 | 13 | const Wrapper = styled(`div`)` 14 | position: relative; 15 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 16 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 17 | animation-fill-mode: forwards; 18 | animation: ${rotate} 2s 0s infinite linear; 19 | ` 20 | 21 | const Circle = styled(`div`)` 22 | position: absolute; 23 | top: ${({ version }) => (version % 2 ? `0` : `auto`)}; 24 | bottom: ${({ version }) => (version % 2 ? `auto` : `0`)}; 25 | height: ${({ size, sizeUnit }) => `${size / 2}${sizeUnit}`}; 26 | width: ${({ size, sizeUnit }) => `${size / 2}${sizeUnit}`}; 27 | background-color: ${({ color }) => color}; 28 | border-radius: 100%; 29 | animation-fill-mode: forwards; 30 | animation: ${({ version }) => `${bounce} 2s ${(version === 2 ? `-1s` : `0s`)} infinite linear`}; 31 | ` 32 | 33 | export const DotLoader = { 34 | functional: true, 35 | props: { 36 | loading: { type: Boolean, default: true }, 37 | color: { type: String, default: `#000000` }, 38 | size: { type: Number, default: 60 }, 39 | sizeUnit: { type: String, default: `px` } 40 | }, 41 | render(h, { props, data }) { 42 | return props.loading ? ( 43 | 48 | {range(2, 1).map(i => ( 49 | 55 | ))} 56 | 57 | ) : null 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/fadeLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { css, keyframes } from 'vue-emotion' 2 | import { characterRange, range, zip } from '../utils' 3 | 4 | const fade = keyframes` 5 | 50% {opacity: 0.3} 6 | 100% {opacity: 1} 7 | ` 8 | 9 | const rad = 20 10 | const quarter = rad / 2 + rad / 5.5 11 | 12 | const Wrapper = styled(`div`)` 13 | position: relative; 14 | font-size: 0; 15 | top: ${rad}px; 16 | left: ${rad}px; 17 | width: ${rad * 3}px; 18 | height: ${rad * 3}px; 19 | ` 20 | 21 | const Bar = styled(`div`)` 22 | position: absolute; 23 | width: ${({ width, widthUnit }) => `${width}${widthUnit}`}; 24 | height: ${({ height, heightUnit }) => `${height}${heightUnit}`}; 25 | margin: ${({ margin }) => margin}; 26 | background-color: ${({ color }) => color}; 27 | border-radius: ${({ radius, radiusUnit }) => `${radius}${radiusUnit}`}; 28 | transition: 2s; 29 | animation-fill-mode: 'both'; 30 | animation: ${({ version }) => `${fade} 1.2s ${version * 0.12}s infinite ease-in-out`}; 31 | ${({ variation }) => variation} 32 | ` 33 | 34 | const styles = { 35 | a: css` 36 | top: ${rad}px; 37 | left: 0; 38 | `, 39 | b: css` 40 | top: ${quarter}px; 41 | left: ${quarter}px; 42 | transform: rotate(-45deg); 43 | `, 44 | c: css` 45 | top: 0; 46 | left: ${rad}px; 47 | transform: rotate(90deg); 48 | `, 49 | d: css` 50 | top: ${-quarter}px; 51 | left: ${quarter}px; 52 | transform: rotate(45deg); 53 | `, 54 | e: css` 55 | top: ${-rad}px; 56 | left: 0; 57 | `, 58 | f: css` 59 | top: ${-quarter}px; 60 | left: ${-quarter}px; 61 | transform: rotate(-45deg); 62 | `, 63 | g: css` 64 | top: 0; 65 | left: ${-rad}px; 66 | transform: rotate(90deg); 67 | `, 68 | h: css` 69 | top: ${quarter}px; 70 | left: ${-quarter}px; 71 | transform: rotate(45deg); 72 | ` 73 | } 74 | 75 | const rows = zip(characterRange(`a`, `i`).split(``), range(9, 1)) 76 | 77 | export const FadeLoader = { 78 | functional: true, 79 | props: { 80 | loading: { type: Boolean, default: true }, 81 | color: { type: String, default: `#000000` }, 82 | height: { type: Number, default: 15 }, 83 | width: { type: Number, default: 5 }, 84 | margin: { type: String, default: `2px` }, 85 | radius: { type: Number, default: 2 }, 86 | widthUnit: { type: String, default: `px` }, 87 | heightUnit: { type: String, default: `px` }, 88 | radiusUnit: { type: String, default: `px` } 89 | }, 90 | render(h, { props, data }) { 91 | return props.loading ? ( 92 | 93 | {rows.map(([style, i]) => ( 94 | 106 | ))} 107 | 108 | ) : null 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/components/gridLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const grid = keyframes` 5 | 0% {transform: scale(1)} 6 | 50% {transform: scale(0.5); opacity: 0.7} 7 | 100% {transform: scale(1);opacity: 1} 8 | ` 9 | 10 | const random = top => Math.random() * top 11 | 12 | const Wrapper = styled(`div`)` 13 | width: ${({ margin, size, sizeUnit }) => `${parseFloat(size) * 3 + parseFloat(margin) * 6}${sizeUnit}`}; 14 | font-size: 0; 15 | ` 16 | 17 | const Circle = styled(`div`)` 18 | display: inline-block; 19 | background-color: ${({ color }) => color}; 20 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 21 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 22 | margin: ${({ margin }) => margin}; 23 | border-radius: 100%; 24 | animation-fill-mode: 'both'; 25 | animation: ${({ rand }) => `${grid} ${rand / 100 + 0.6}s ${rand / 100 - 0.2}s infinite ease`}; 26 | ` 27 | 28 | export const GridLoader = { 29 | functional: true, 30 | props: { 31 | loading: { type: Boolean, default: true }, 32 | color: { type: String, default: `#000000` }, 33 | size: { type: Number, default: 15 }, 34 | margin: { type: String, default: `2px` }, 35 | sizeUnit: { type: String, default: `px` } 36 | }, 37 | render(h, { props, data }) { 38 | return props.loading ? ( 39 | 45 | {range(9).map(_ => ( 46 | 53 | ))} 54 | 55 | ) : null 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/hashLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { calculateRgba, range } from '../utils' 3 | 4 | const thickness = size => size / 5 5 | const lat = size => (size - thickness(size)) / 2 6 | const offset = size => lat(size) - thickness(size) 7 | const getColor = color => calculateRgba(color, 0.75) 8 | 9 | const before = (size, sizeUnit, color) => keyframes` 10 | 0% {width: ${thickness(size)}px;box-shadow: ${lat(size)}px ${-offset( 11 | size 12 | )}px ${getColor(color)}, ${-lat(size)}px ${offset(size)}px ${getColor(color)}} 13 | 35% {width: ${`${size}${sizeUnit}`};box-shadow: 0 ${-offset( 14 | size 15 | )}px ${getColor(color)}, 0 ${offset(size)}px ${getColor(color)}} 16 | 70% {width: ${thickness(size)}px;box-shadow: ${-lat(size)}px ${-offset( 17 | size 18 | )}px ${getColor(color)}, ${lat(size)}px ${offset(size)}px ${getColor(color)}} 19 | 100% {box-shadow: ${lat(size)}px ${-offset(size)}px ${getColor( 20 | color 21 | )}, ${-lat(size)}px ${offset(size)}px ${getColor(color)}} 22 | ` 23 | 24 | const after = (size, sizeUnit, color) => keyframes` 25 | 0% {height: ${thickness(size)}px;box-shadow: ${offset(size)}px ${lat( 26 | size 27 | )}px ${getColor(color)}, ${-offset(size)}px ${-lat(size)}px ${getColor(color)}} 28 | 35% {height: ${`${size}${sizeUnit}`};box-shadow: ${offset( 29 | size 30 | )}px 0 ${getColor(color)}, ${-offset(size)}px 0 ${getColor(color)}} 31 | 70% {height: ${thickness(size)}px;box-shadow: ${offset(size)}px ${-lat( 32 | size 33 | )}px ${getColor(color)}, ${-offset(size)}px ${lat(size)}px ${getColor(color)}} 34 | 100% {box-shadow: ${offset(size)}px ${lat(size)}px ${getColor( 35 | color 36 | )}, ${-offset(size)}px ${-lat(size)}px ${getColor(color)}} 37 | ` 38 | 39 | const Wrapper = styled(`div`)` 40 | position: relative; 41 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 42 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 43 | transform: rotate(165deg); 44 | ` 45 | 46 | const Lines = styled(`div`)` 47 | position: absolute; 48 | top: 50%; 49 | left: 50%; 50 | display: block; 51 | width: ${({ size, sizeUnit }) => `${size / 5}${sizeUnit}`}; 52 | height: ${({ size, sizeUnit }) => `${size / 5}${sizeUnit}`}; 53 | border-radius: ${({ size, sizeUnit }) => `${size / 10}${sizeUnit}`}; 54 | transform: translate(-50%, -50%); 55 | animation: ${({ size, sizeUnit, color, version }) => `${(version === 1 ? before(size, sizeUnit, color) : after(size, sizeUnit, color))} 2s infinite normal none running`}; 56 | content: ''; 57 | ` 58 | 59 | export const HashLoader = { 60 | functional: true, 61 | props: { 62 | loading: { type: Boolean, default: true }, 63 | color: { type: String, default: `#000000` }, 64 | size: { type: Number, default: 50 }, 65 | sizeUnit: { type: String, default: `px` } 66 | }, 67 | render(h, { props, data }) { 68 | return props.loading ? ( 69 | 74 | {range(2, 1).map(i => ( 75 | 81 | ))} 82 | 83 | ) : null 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { BarLoader } from './barLoader' 2 | export { BeatLoader } from './beatLoader' 3 | export { BounceLoader } from './bounceLoader' 4 | export { CircleLoader } from './circleLoader' 5 | export { ClimbingBoxLoader } from './climbingBoxLoader' 6 | export { ClipLoader } from './clipLoader' 7 | export { DotLoader } from './dotLoader' 8 | export { FadeLoader } from './fadeLoader' 9 | export { GridLoader } from './gridLoader' 10 | export { HashLoader } from './hashLoader' 11 | export { MoonLoader } from './moonLoader' 12 | export { PacmanLoader } from './pacmanLoader' 13 | export { PropagateLoader } from './propagateLoader' 14 | export { PulseLoader } from './pulseLoader' 15 | export { RingLoader } from './ringLoader' 16 | export { RiseLoader } from './riseLoader' 17 | export { RotateLoader } from './rotateLoader' 18 | export { ScaleLoader } from './scaleLoader' 19 | export { SkewLoader } from './skewLoader' 20 | export { SquareLoader } from './squareLoader' 21 | export { SyncLoader } from './syncLoader' 22 | -------------------------------------------------------------------------------- /src/components/moonLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { css, keyframes } from 'vue-emotion' 2 | 3 | const moon = keyframes` 4 | 100% {transform: rotate(360deg)} 5 | ` 6 | 7 | const moonSize = size => (size / 7).toFixed(5) 8 | 9 | const ballStyle = (size, sizeUnit) => css` 10 | width: ${`${size}${sizeUnit}`}; 11 | height: ${`${size}${sizeUnit}`}; 12 | border-radius: 100%; 13 | ` 14 | 15 | const Wrapper = styled(`div`)` 16 | position: relative; 17 | width: ${({ size, sizeUnit }) => `${size + moonSize(size) * 2}${sizeUnit}`}; 18 | height: ${({ size, sizeUnit }) => `${size + moonSize(size) * 2}${sizeUnit}`}; 19 | animation: ${moon} 0.6s linear 0s infinite normal forwards running; 20 | box-sizing: content-box; 21 | ` 22 | 23 | const Moon = styled(`div`)` 24 | position: absolute; 25 | top: ${({ size, sizeUnit }) => `${size / 2 - moonSize(size) / 2}${sizeUnit}`}; 26 | background-color: ${({ color }) => color}; 27 | opacity: 0.8; 28 | animation: ${moon} 0.6s linear 0s infinite normal forwards running; 29 | box-sizing: content-box; 30 | ${({ size, sizeUnit }) => ballStyle(moonSize(size), sizeUnit)}; 31 | ` 32 | 33 | const Ring = styled(`div`)` 34 | border-width: ${({ size }) => `${moonSize(size)}px`}; 35 | border-style: solid; 36 | border-color: ${({ color }) => `${color}`}; 37 | border-image: initial; 38 | opacity: 0.1; 39 | box-sizing: content-box; 40 | ${({ size, sizeUnit }) => ballStyle(size, sizeUnit)}; 41 | ` 42 | 43 | export const MoonLoader = { 44 | functional: true, 45 | props: { 46 | loading: { type: Boolean, default: true }, 47 | color: { type: String, default: `#000000` }, 48 | size: { type: Number, default: 60 }, 49 | sizeUnit: { type: String, default: `px` } 50 | }, 51 | render(h, { props, data }) { 52 | return props.loading ? ( 53 | 58 | 59 | 64 | 65 | ) : null 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/pacmanLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | // This returns an animation 5 | const pacman = [ 6 | keyframes` 7 | 0% {transform: rotate(0deg)} 8 | 50% {transform: rotate(-44deg)} 9 | `, 10 | keyframes` 11 | 0% {transform: rotate(0deg)} 12 | 50% {transform: rotate(44deg)} 13 | ` 14 | ] 15 | 16 | const Wrapper = styled(`div`)` 17 | position: relative; 18 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 19 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 20 | font-size: 0; 21 | ` 22 | 23 | const s1 = (size, sizeUnit) => `${size}${sizeUnit} solid transparent` 24 | const s2 = (size, sizeUnit, color) => `${size}${sizeUnit} solid ${color}` 25 | 26 | const Pacman = styled(`div`)` 27 | position: absolute; 28 | width: 0; 29 | height: 0; 30 | border-top: ${({ size, sizeUnit, color, version }) => (version === 0 ? s1(size, sizeUnit) : s2(size, sizeUnit, color))}; 31 | border-left: ${({ size, sizeUnit, color }) => s2(size, sizeUnit, color)}; 32 | border-bottom: ${({ size, sizeUnit, color, version }) => (version === 0 ? s2(size, sizeUnit, color) : s1(size, sizeUnit))}; 33 | border-right: ${({ size, sizeUnit }) => s1(size, sizeUnit)}; 34 | border-radius: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 35 | animation: ${({ version }) => pacman[version]} ease-in-out 0.8s infinite normal both running; 36 | ` 37 | 38 | const ballAnim = (size, sizeUnit) => keyframes` 39 | 75% {opacity: 0.7} 40 | 100% {transform: translate(${-4 * size}${sizeUnit}, ${-size / 4}${sizeUnit})} 41 | ` 42 | 43 | const Ball = styled(`div`)` 44 | position: absolute; 45 | top: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 46 | left: ${({ size, sizeUnit }) => `${size * 4}${sizeUnit}`}; 47 | width: ${({ size, sizeUnit }) => `${size / 2.5}${sizeUnit}`}; 48 | height: ${({ size, sizeUnit }) => `${size / 2.5}${sizeUnit}`}; 49 | margin: ${({ margin }) => margin}; 50 | border-radius: 100%; 51 | background-color: ${({ color }) => color}; 52 | transform: ${({ size, sizeUnit }) => `translate(0, ${-size / 4}${sizeUnit})`}; 53 | animation: ${({ size, sizeUnit, version }) => `${ballAnim(size, sizeUnit)} 1s linear ${version * 0.25}s infinite normal both running`}; 54 | ` 55 | 56 | export const PacmanLoader = { 57 | functional: true, 58 | props: { 59 | loading: { type: Boolean, default: true }, 60 | color: { type: String, default: `#000000` }, 61 | size: { type: Number, default: 25 }, 62 | sizeUnit: { type: String, default: `px` }, 63 | margin: { type: String, default: `2px` } 64 | }, 65 | render(h, { props, data }) { 66 | return props.loading ? ( 67 | 72 | 78 | 84 | {range(4, 2).map(i => ( 85 | 92 | ))} 93 | 94 | ) : null 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/components/propagateLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | // 1.5 4.5 7.5 5 | const distance = [1, 3, 5] 6 | 7 | const propagate = [ 8 | keyframes` 9 | 25% {transform: translateX(-${distance[0]}rem) scale(0.75)} 10 | 50% {transform: translateX(-${distance[1]}rem) scale(0.6)} 11 | 75% {transform: translateX(-${distance[2]}rem) scale(0.5)} 12 | 95% {transform: translateX(0rem) scale(1)} 13 | `, 14 | keyframes` 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[1]}rem) scale(0.6)} 18 | 95% {transform: translateX(0rem) scale(1)} 19 | `, 20 | keyframes` 21 | 25% {transform: translateX(-${distance[0]}rem) scale(0.75)} 22 | 75% {transform: translateX(-${distance[0]}rem) scale(0.75)} 23 | 95% {transform: translateX(0rem) scale(1)} 24 | `, 25 | keyframes` 26 | 25% {transform: translateX(${distance[0]}rem) scale(0.75)} 27 | 75% {transform: translateX(${distance[0]}rem) scale(0.75)} 28 | 95% {transform: translateX(0rem) scale(1)} 29 | `, 30 | keyframes` 31 | 25% {transform: translateX(${distance[0]}rem) scale(0.75)} 32 | 50% {transform: translateX(${distance[1]}rem) scale(0.6)} 33 | 75% {transform: translateX(${distance[1]}rem) scale(0.6)} 34 | 95% {transform: translateX(0rem) scale(1)} 35 | `, 36 | keyframes` 37 | 25% {transform: translateX(${distance[0]}rem) scale(0.75)} 38 | 50% {transform: translateX(${distance[1]}rem) scale(0.6)} 39 | 75% {transform: translateX(${distance[2]}rem) scale(0.5)} 40 | 95% {transform: translateX(0rem) scale(1)} 41 | ` 42 | ] 43 | 44 | const Wrapper = styled(`div`)` 45 | position: relative; 46 | ` 47 | 48 | const Circle = styled(`div`)` 49 | position: absolute; 50 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 51 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 52 | border-radius: 50%; 53 | background: ${({ color }) => color}; 54 | font-size: ${({ size, sizeUnit }) => `${size / 3}${sizeUnit}`}; 55 | animation: ${({ version }) => propagate[version]} 1.5s infinite; 56 | animation-fill-mode: forwards; 57 | ` 58 | 59 | export const PropagateLoader = { 60 | functional: true, 61 | props: { 62 | loading: { type: Boolean, default: true }, 63 | color: { type: String, default: `#000000` }, 64 | size: { type: Number, default: 15 }, 65 | sizeUnit: { type: String, default: `px` } 66 | }, 67 | render(h, { props, data }) { 68 | return props.loading ? ( 69 | 70 | {range(6).map(i => ( 71 | 77 | ))} 78 | 79 | ) : null 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/components/pulseLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const pulse = keyframes` 5 | 0% {transform: scale(1);opacity: 1} 6 | 45% {transform: scale(0.1);opacity: 0.7} 7 | 80% {transform: scale(1);opacity: 1} 8 | ` 9 | 10 | const Circle = styled(`div`)` 11 | display: inline-block; 12 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 13 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 14 | margin: ${({ margin }) => margin}; 15 | border-radius: 100%; 16 | background-color: ${({ color }) => color}; 17 | animation: ${({ version }) => `${pulse} 0.75s ${version * 0.12}s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08)`}; 18 | animation-fill-mode: both; 19 | ` 20 | 21 | export const PulseLoader = { 22 | functional: true, 23 | props: { 24 | loading: { type: Boolean, default: true }, 25 | color: { type: String, default: `#000000` }, 26 | size: { type: Number, default: 15 }, 27 | sizeUnit: { type: String, default: `px` }, 28 | margin: { type: String, default: `2px` } 29 | }, 30 | render(h, { props, data }) { 31 | return props.loading ? ( 32 |
33 | {range(3, 1).map(i => ( 34 | 41 | ))} 42 |
43 | ) : null 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/ringLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const right = keyframes` 5 | 0% {transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg)} 6 | 100% {transform: rotateX(180deg) rotateY(360deg) rotateZ(360deg)} 7 | ` 8 | 9 | const left = keyframes` 10 | 0% {transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg)} 11 | 100% {transform: rotateX(360deg) rotateY(180deg) rotateZ(360deg)} 12 | ` 13 | 14 | const Wrapper = styled(`div`)` 15 | position: relative; 16 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 17 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 18 | ` 19 | 20 | const Ring = styled(`div`)` 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 25 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 26 | border: ${({ size, sizeUnit, color }) => `${size / 10}${sizeUnit} solid ${color}`}; 27 | border-radius: 100%; 28 | opacity: 0.4; 29 | animation: ${({ version }) => `${(version === 1 ? right : left)} 2s 0s infinite linear`}; 30 | animation-fill-mode: forwards; 31 | perspective: 800px; 32 | ` 33 | 34 | export const RingLoader = { 35 | functional: true, 36 | props: { 37 | loading: { type: Boolean, default: true }, 38 | color: { type: String, default: `#000000` }, 39 | size: { type: Number, default: 60 }, 40 | sizeUnit: { type: String, default: `px` } 41 | }, 42 | render(h, { props, data }) { 43 | return props.loading ? ( 44 | 49 | {range(2, 1).map(i => ( 50 | 56 | ))} 57 | 58 | ) : null 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/riseLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const riseAmount = 30 5 | 6 | const even = keyframes` 7 | 0% {transform: scale(1.1)} 8 | 25% {translateY(-${riseAmount}px)} 9 | 50% {transform: scale(0.4)} 10 | 75% {transform: translateY(${riseAmount}px)} 11 | 100% {transform: translateY(0) scale(1.0)} 12 | ` 13 | 14 | const odd = keyframes` 15 | 0% {transform: scale(0.4)} 16 | 25% {translateY(${riseAmount}px)} 17 | 50% {transform: scale(1.1)} 18 | 75% {transform: translateY(${-riseAmount}px)} 19 | 100% {transform: translateY(0) scale(0.75)} 20 | ` 21 | 22 | const Circle = styled(`div`)` 23 | display: inline-block; 24 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 25 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 26 | margin: ${({ margin }) => margin}; 27 | border-radius: 100%; 28 | background-color: ${({ color }) => color}; 29 | animation: ${({ version }) => `${(version % 2 === 0 ? even : odd)} 1s 0s infinite cubic-bezier(0.15, 0.46, 0.9, 0.6)`}; 30 | animation-fill-mode: both; 31 | ` 32 | 33 | export const RiseLoader = { 34 | functional: true, 35 | props: { 36 | loading: { type: Boolean, default: true }, 37 | color: { type: String, default: `#000000` }, 38 | size: { type: Number, default: 15 }, 39 | sizeUnit: { type: String, default: `px` }, 40 | margin: { type: String, default: `2px` } 41 | }, 42 | render(h, { props, data }) { 43 | return props.loading ? ( 44 |
45 | {range(5, 1).map(i => ( 46 | 53 | ))} 54 |
55 | ) : null 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/rotateLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { css, keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const rotate = keyframes` 5 | 0% {transform: rotate(0deg)} 6 | 50% {transform: rotate(180deg)} 7 | 100% {transform: rotate(360deg)} 8 | ` 9 | 10 | const fill = (color, margin, size, sizeUnit) => css` 11 | width: ${`${size}${sizeUnit}`}; 12 | height: ${`${size}${sizeUnit}`}; 13 | margin: ${margin}; 14 | border-radius: 100%; 15 | background-color: ${color}; 16 | ` 17 | 18 | const Wrapper = styled(`div`)` 19 | position: relative; 20 | display: inline-block; 21 | animation: ${rotate} 1s 0s infinite cubic-bezier(0.7, -0.13, 0.22, 0.86); 22 | animation-fill-mode: both; 23 | ${({ color, margin, size, sizeUnit }) => fill(color, margin, size, sizeUnit)} 24 | ` 25 | 26 | const Circle = styled(`div`)` 27 | position: absolute; 28 | top: 0; 29 | left: ${({ side }) => `${side ? -28 : 25}px`}; 30 | opacity: 0.8; 31 | ${({ color, margin, size, sizeUnit }) => `${fill(color, margin, size, sizeUnit)}`} 32 | ` 33 | 34 | export const RotateLoader = { 35 | functional: true, 36 | props: { 37 | loading: { type: Boolean, default: true }, 38 | color: { type: String, default: `#000000` }, 39 | size: { type: Number, default: 15 }, 40 | sizeUnit: { type: String, default: `px` }, 41 | margin: { type: String, default: `2px` } 42 | }, 43 | render(h, { props, data }) { 44 | return props.loading ? ( 45 | 52 | {range(2).map(i => ( 53 | 60 | ))} 61 | 62 | ) : null 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/scaleLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const scale = keyframes` 5 | 0% {transform: scaley(1.0)} 6 | 50% {transform: scaley(0.4)} 7 | 100% {transform: scaley(1.0)} 8 | ` 9 | 10 | const Bar = styled(`div`)` 11 | display: inline-block; 12 | width: ${({ width, widthUnit }) => `${width}${widthUnit}`}; 13 | height: ${({ height, heightUnit }) => `${height}${heightUnit}`}; 14 | margin: ${({ margin }) => margin}; 15 | border-radius: ${({ radius, radiusUnit }) => `${radius}${radiusUnit}`}; 16 | background-color: ${({ color }) => color}; 17 | animation: ${({ version }) => `${scale} 1s cubic-bezier(0.2, 0.68, 0.18, 1.08) ${version * 0.1}s infinite normal both running`}; 18 | ` 19 | 20 | export const ScaleLoader = { 21 | functional: true, 22 | props: { 23 | loading: { type: Boolean, default: true }, 24 | color: { type: String, default: `#000000` }, 25 | height: { type: Number, default: 35 }, 26 | heightUnit: { type: String, default: `px` }, 27 | width: { type: Number, default: 4 }, 28 | widthUnit: { type: String, default: `px` }, 29 | radius: { type: Number, default: 2 }, 30 | radiusUnit: { type: String, default: `px` }, 31 | margin: { type: String, default: `2px` } 32 | }, 33 | render(h, { props, data }) { 34 | return props.loading ? ( 35 |
36 | {range(5, 1).map(i => ( 37 | 48 | ))} 49 |
50 | ) : null 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/skewLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | 3 | const skew = keyframes` 4 | 25% {transform: perspective(100px) rotateX(180deg) rotateY(0)} 5 | 50% {transform: perspective(100px) rotateX(180deg) rotateY(180deg)} 6 | 75% {transform: perspective(100px) rotateX(0) rotateY(180deg)} 7 | 100% {transform: perspective(100px) rotateX(0) rotateY(0)} 8 | ` 9 | 10 | const Triangle = styled(`div`)` 11 | display: inline-block; 12 | width: 0; 13 | height: 0; 14 | border-left: ${({ size, sizeUnit }) => `${size}${sizeUnit} solid transparent`}; 15 | border-right: ${({ size, sizeUnit }) => `${size}${sizeUnit} solid transparent`}; 16 | border-bottom: ${({ size, sizeUnit, color }) => `${size}${sizeUnit} solid ${color}`}; 17 | animation: ${skew} 3s 0s infinite cubic-bezier(.09,.57,.49,.9); 18 | animation-fill-mode: both; 19 | ` 20 | 21 | export const SkewLoader = { 22 | functional: true, 23 | props: { 24 | loading: { type: Boolean, default: true }, 25 | color: { type: String, default: `#000000` }, 26 | size: { type: Number, default: 20 }, 27 | sizeUnit: { type: String, default: `px` } 28 | }, 29 | render(h, { props, data }) { 30 | return props.loading ? ( 31 | 37 | ) : null 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/squareLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | 3 | const square = keyframes` 4 | 25% {transform: rotateX(180deg) rotateY(0)} 5 | 50% {transform: rotateX(180deg) rotateY(180deg)} 6 | 75% {transform: rotateX(0) rotateY(180deg)} 7 | 100% {transform: rotateX(0) rotateY(0)} 8 | ` 9 | 10 | const Square = styled(`div`)` 11 | display: inline-block; 12 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 13 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 14 | background-color: ${({ color }) => color}; 15 | animation: ${square} 3s 0s infinite cubic-bezier(0.09, 0.57, 0.49, 0.9); 16 | animation-fill-mode: both; 17 | ` 18 | 19 | export const SquareLoader = { 20 | functional: true, 21 | props: { 22 | loading: { type: Boolean, default: true }, 23 | color: { type: String, default: `#000000` }, 24 | size: { type: Number, default: 50 }, 25 | sizeUnit: { type: String, default: `px` } 26 | }, 27 | render(h, { props, data }) { 28 | return props.loading ? ( 29 | 35 | ) : null 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/syncLoader.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'vue-emotion' 2 | import { range } from '../utils' 3 | 4 | const sync = keyframes` 5 | 33% {transform: translateY(10px)} 6 | 66% {transform: translateY(-10px)} 7 | 100% {transform: translateY(0)} 8 | ` 9 | 10 | const Circle = styled(`div`)` 11 | display: inline-block; 12 | width: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 13 | height: ${({ size, sizeUnit }) => `${size}${sizeUnit}`}; 14 | margin: ${({ margin }) => margin}; 15 | border-radius: 100%; 16 | background-color: ${({ color }) => color}; 17 | animation: ${({ version }) => `${sync} 0.6s ease-in-out ${version * 0.07}s infinite normal both running`}; 18 | box-sizing: content-box; 19 | ` 20 | 21 | export const SyncLoader = { 22 | functional: true, 23 | props: { 24 | loading: { type: Boolean, default: true }, 25 | color: { type: String, default: `#000000` }, 26 | size: { type: Number, default: 15 }, 27 | sizeUnit: { type: String, default: `px` }, 28 | margin: { type: String, default: `2px` } 29 | }, 30 | render(h, { props, data }) { 31 | return props.loading ? ( 32 |
33 | {range(3, 1).map(i => ( 34 | 41 | ))} 42 |
43 | ) : null 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as Components from './components' 2 | 3 | export function install(Vue) { 4 | if (install.installed) return 5 | install.installed = true 6 | Object.entries(Components).forEach(([name, component]) => { 7 | Vue.component(name, component) 8 | }) 9 | } 10 | 11 | export const VueSpinners = { 12 | install 13 | } 14 | 15 | let GlobalVue = null 16 | if (typeof window !== `undefined`) { 17 | GlobalVue = window.Vue 18 | } else if (typeof global !== `undefined`) { 19 | GlobalVue = global.Vue 20 | } 21 | if (GlobalVue) { 22 | GlobalVue.use(VueSpinners) 23 | } 24 | 25 | export default Components 26 | export * from './components' 27 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export const calculateRgba = (input, opacity) => { 2 | let color 3 | if (input[0] === `#`) { 4 | color = input.slice(1) 5 | } 6 | 7 | if (color.length === 3) { 8 | let res = `` 9 | color.split(``).forEach(c => { 10 | res += c 11 | res += c 12 | }) 13 | color = res 14 | } 15 | 16 | const rgbValues = color 17 | .match(/.{2}/g) 18 | .map(hex => parseInt(hex, 16)) 19 | .join(`, `) 20 | return `rgba(${rgbValues}, ${opacity})` 21 | } 22 | 23 | export const range = (size, startAt = 0) => 24 | [...Array(size).keys()].map(i => i + startAt) 25 | 26 | export const characterRange = (startChar, endChar) => 27 | String.fromCharCode( 28 | ...range( 29 | endChar.charCodeAt(0) - startChar.charCodeAt(0), 30 | startChar.charCodeAt(0) 31 | ) 32 | ) 33 | 34 | export const zip = (arr, ...arrs) => 35 | arr.map((val, i) => arrs.reduce((list, curr) => [...list, curr[i]], [val])) 36 | --------------------------------------------------------------------------------