├── .dependabot
└── config.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── 1-bug-report.md
│ └── 2-feature-request.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .storybook
├── addons.js
├── config.js
└── webpack.config.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── azure-pipelines.yml
├── babel.config.js
├── docs
└── images
│ └── repo-banner.gif
├── examples
├── README.md
└── server-side-rendering
│ ├── .gitignore
│ ├── README.md
│ ├── babel.config.js
│ ├── package.json
│ ├── screenshot.gif
│ ├── src
│ ├── App.tsx
│ ├── client
│ │ └── index.tsx
│ └── server
│ │ └── index.tsx
│ ├── webpack.config.js
│ └── yarn.lock
├── package.json
├── rollup.config.js
├── src
├── MDSpinner.tsx
├── constants.ts
├── index.ts
├── ssr-behavior.tsx
├── styles
│ ├── get-styles.ts
│ └── keyframes.ts
└── utils
│ └── manipulation.ts
├── stories
├── __snapshots__
│ └── index.stories.storyshot
└── index.stories.tsx
├── test
├── MDSpinner.test.tsx
└── storyshots.test.js
├── tsconfig.json
└── yarn.lock
/.dependabot/config.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 | update_configs:
3 | - package_manager: "javascript"
4 | directory: "/"
5 | update_schedule: "live"
6 |
7 | default_assignees:
8 | - "tsuyoshiwada"
9 |
10 | default_labels:
11 | - "dependencies"
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = false
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /dist
2 | /storybook-static
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: "@typescript-eslint/parser",
3 | parserOptions: {
4 | sourceType: "module",
5 | project: "./tsconfig.json",
6 | ecmaFeatures: {
7 | jsx: true
8 | }
9 | },
10 | plugins: ["@typescript-eslint", "react"],
11 | extends: [
12 | "eslint:recommended",
13 | "plugin:@typescript-eslint/recommended",
14 | "prettier",
15 | "prettier/@typescript-eslint"
16 | ],
17 | env: {
18 | node: true,
19 | browser: true,
20 | jest: true
21 | },
22 | globals: {
23 | page: true,
24 | browser: true,
25 | context: true,
26 | jestPuppeteer: true
27 | },
28 | settings: {
29 | react: {
30 | version: "detect"
31 | }
32 | },
33 | rules: {
34 | "@typescript-eslint/no-explicit-any": "off",
35 | "@typescript-eslint/no-unused-vars": "off",
36 | "@typescript-eslint/explicit-function-return-type": "off",
37 | "react/display-name": "off",
38 | "react/prop-types": "off"
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1-bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐛 Bug Report
3 | about: Bugs, missing documentation, or unexpected behavior.
4 | ---
5 |
6 |
7 |
8 | ## Expected Behavior
9 |
10 | {Please write here}
11 |
12 | ## Actual Behavior
13 |
14 | {Please write here}
15 |
16 | ## Steps to Reproduce (including precondition)
17 |
18 | {Please write here}
19 |
20 | ## Screenshot on This Problem (if possible)
21 |
22 | {Please write here}
23 |
24 | ## Your Environment
25 |
26 | - OS: {Please write here}
27 | - react-md-spinner version: {Please write here}
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2-feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🚀 Feature Request
3 | about: Suggest an idea for this project.
4 | ---
5 |
6 |
7 |
8 | ## Description
9 |
10 | {Please write here}
11 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## What does this do / why do we need it?
4 |
5 | {Please write here}
6 |
7 | ## How this PR fixes the problem?
8 |
9 | {Please write here}
10 |
11 | ## What should your reviewer look out for in this PR?
12 |
13 | {Please write here}
14 |
15 | ## Check lists
16 |
17 | - [ ] Test passed
18 | - [ ] Coding style (indentation, etc)
19 |
20 | ## Additional Comments (if any)
21 |
22 | {Please write here}
23 |
24 | ## Which issue(s) does this PR fix?
25 |
26 |
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 | /storybook-static
3 | /tmp
4 |
5 | # Created by https://www.gitignore.io/api/osx,windows,node
6 | # Edit at https://www.gitignore.io/?templates=osx,windows,node
7 |
8 | ### Node ###
9 | # Logs
10 | logs
11 | *.log
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | lerna-debug.log*
16 |
17 | # Diagnostic reports (https://nodejs.org/api/report.html)
18 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
19 |
20 | # Runtime data
21 | pids
22 | *.pid
23 | *.seed
24 | *.pid.lock
25 |
26 | # Directory for instrumented libs generated by jscoverage/JSCover
27 | lib-cov
28 |
29 | # Coverage directory used by tools like istanbul
30 | coverage
31 |
32 | # nyc test coverage
33 | .nyc_output
34 |
35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
36 | .grunt
37 |
38 | # Bower dependency directory (https://bower.io/)
39 | bower_components
40 |
41 | # node-waf configuration
42 | .lock-wscript
43 |
44 | # Compiled binary addons (https://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | # Dependency directories
48 | node_modules/
49 | jspm_packages/
50 |
51 | # TypeScript v1 declaration files
52 | typings/
53 |
54 | # Optional npm cache directory
55 | .npm
56 |
57 | # Optional eslint cache
58 | .eslintcache
59 |
60 | # Optional REPL history
61 | .node_repl_history
62 |
63 | # Output of 'npm pack'
64 | *.tgz
65 |
66 | # Yarn Integrity file
67 | .yarn-integrity
68 |
69 | # dotenv environment variables file
70 | .env
71 | .env.test
72 |
73 | # parcel-bundler cache (https://parceljs.org/)
74 | .cache
75 |
76 | # next.js build output
77 | .next
78 |
79 | # nuxt.js build output
80 | .nuxt
81 |
82 | # vuepress build output
83 | .vuepress/dist
84 |
85 | # Serverless directories
86 | .serverless/
87 |
88 | # FuseBox cache
89 | .fusebox/
90 |
91 | # DynamoDB Local files
92 | .dynamodb/
93 |
94 | ### OSX ###
95 | # General
96 | .DS_Store
97 | .AppleDouble
98 | .LSOverride
99 |
100 | # Icon must end with two \r
101 | Icon
102 |
103 | # Thumbnails
104 | ._*
105 |
106 | # Files that might appear in the root of a volume
107 | .DocumentRevisions-V100
108 | .fseventsd
109 | .Spotlight-V100
110 | .TemporaryItems
111 | .Trashes
112 | .VolumeIcon.icns
113 | .com.apple.timemachine.donotpresent
114 |
115 | # Directories potentially created on remote AFP share
116 | .AppleDB
117 | .AppleDesktop
118 | Network Trash Folder
119 | Temporary Items
120 | .apdisk
121 |
122 | ### Windows ###
123 | # Windows thumbnail cache files
124 | Thumbs.db
125 | ehthumbs.db
126 | ehthumbs_vista.db
127 |
128 | # Dump file
129 | *.stackdump
130 |
131 | # Folder config file
132 | [Dd]esktop.ini
133 |
134 | # Recycle Bin used on file shares
135 | $RECYCLE.BIN/
136 |
137 | # Windows Installer files
138 | *.cab
139 | *.msi
140 | *.msix
141 | *.msm
142 | *.msp
143 |
144 | # Windows shortcuts
145 | *.lnk
146 |
147 | # End of https://www.gitignore.io/api/osx,windows,node
148 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import "@storybook/addon-actions/register";
2 | import "@storybook/addon-links/register";
3 | import "@storybook/addon-storysource/register";
4 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from "@storybook/react";
2 | import requireContext from "require-context.macro";
3 |
4 | // automatically import all files ending in *.stories.tsx
5 | const req = requireContext("../stories", true, /\.stories\.tsx$/);
6 |
7 | function loadStories() {
8 | req.keys().forEach(req);
9 | }
10 |
11 | configure(loadStories, module);
12 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | module.exports = ({ config }) => {
4 | config.module.rules.push({
5 | test: /\.(ts|tsx)$/,
6 | include: [
7 | path.resolve(__dirname, "../src"),
8 | path.resolve(__dirname, "../stories")
9 | ],
10 | exclude: /node_modules/,
11 | use: [
12 | {
13 | loader: require.resolve("babel-loader")
14 | },
15 | {
16 | loader: require.resolve("@storybook/addon-storysource/loader"),
17 | options: {
18 | parser: "typescript"
19 | }
20 | }
21 | ]
22 | });
23 |
24 | config.resolve.extensions.push(".ts", ".tsx");
25 |
26 | return config;
27 | };
28 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 1.0.0 (2019-06-09)
4 |
5 | ### New Features
6 |
7 | - Rewrite with TypeScript!
8 | - Small bundle size!!
9 | - Build process using rollup and babel. (support `module` field)
10 | - Storybook examples.
11 |
12 | ### Breaking Changes
13 |
14 | - Remove `useAgent` props. (always vendor prefixes)
15 | - Remove `useAgent` argument on `ssrBehavior` API.
16 |
17 | Changed to use `stylis` instead of `inline-style-prefixer`. Along with that, we now always have a vendor prefix for styles in `@keyframes`.
18 |
19 | These changes have the benefit of reducing the bundle size.
20 |
21 | ### Internal Changes
22 |
23 | - TSLint -> ESLint.
24 | - Using Prettier.
25 | - Using Dependabot.
26 |
27 | ## 0.4.0 (2019-06-01)
28 |
29 | - Replace deprecated lifecycle hook. Thanks [@ChrisBrownie55](https://github.com/ChrisBrownie55) !
30 |
31 | ## 0.3.0 (2018-07-08)
32 |
33 | - Add `borderSize` props.
34 | Thanks [@dbalas](https://github.com/dbalas) !
35 |
36 | ## 0.2.5 (2017-07-22)
37 |
38 | - Changes unused Component types from `undefined` to `{}`
39 | Thanks [@nbgraham](https://github.com/nbgraham) !
40 |
41 | ## 0.2.4 (2017-05-22)
42 |
43 | - Add type declaration file for typescript [#9](https://github.com/tsuyoshiwada/react-md-spinner/pull/9)
44 | Thanks [@devholic](https://github.com/devholic) !!
45 |
46 | ## 0.2.3 (2017-05-08)
47 |
48 | - [rofrischmann/inline-style-prefixer](https://github.com/rofrischmann/inline-style-prefixer) update to `3.0.3`. [#8](https://github.com/tsuyoshiwada/react-md-spinner/pull/8)
49 | Thanks [@MichaelDeBoey](https://github.com/MichaelDeBoey).
50 | - Bump 'react-highlight' dependency.
51 | - Add dependencies [caniuse-api](https://github.com/nyalab/caniuse-api) package.
52 |
53 | ## 0.2.2 (2017-04-21)
54 |
55 | - Use 'prop-types' package instead of React.PropTypes. ([#6](https://github.com/tsuyoshiwada/react-md-spinner/pull/6))
56 | Thanks [@MichaelDeBoey](https://github.com/MichaelDeBoey).
57 |
58 | ## 0.2.1 (2017-03-09)
59 |
60 | - Update dev/dependencies.
61 | - [rofrischmann/inline-style-prefixer](https://github.com/rofrischmann/inline-style-prefixer) update to `3.0.1`.
62 |
63 | ## 0.2.0 (2017-02-02)
64 |
65 | ### New features
66 |
67 | - Full support for Server-Side Rendering. [#3](https://github.com/tsuyoshiwada/react-md-spinner/issues/3).
68 | - Thanks [@cescoferraro](https://github.com/cescoferraro)!
69 | - Add `ssrBehavior` utilities.
70 | - Please checkout [examples](./examples/) directory for details.
71 |
72 | ### Minor changes
73 |
74 | - Add [examples](./examples/).
75 | - Update docs.
76 | - Add this ChangeLog.
77 | - Change to `docs` from `demo` for gh-pages.
78 | - Update dependencies.
79 |
80 | ## 0.1.0 (2016-09-23)
81 |
82 | ### New features
83 |
84 | - Add `userAgent` props. (for Server-Side Rendering)
85 | - Thanks [@ryan-codingintrigue](https://github.com/ryan-codingintrigue)!
86 |
87 | ### Minor changes
88 |
89 | - Update dependencies.
90 |
91 | ## 0.0.3 (2016-07-19)
92 |
93 | - Fix specification of the numeric string on size prop.
94 | - Update dependencies.
95 |
96 | ## 0.0.2 (2016-07-03)
97 |
98 | - Fix npm files in package.json.
99 |
100 | ## 0.0.1 (2016-07-03)
101 |
102 | - First release.
103 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 tsuyoshiwada
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-md-spinner
2 |
3 | 
4 |
5 | [](https://dev.azure.com/wadackel/react-md-spinner/_build/latest?definitionId=1&branchName=master)
6 | [](https://www.npmjs.com/package/react-md-spinner)
7 |
8 | > Material Design spinner components for React.js.
9 |
10 | Live example: https://tsuyoshiwada.github.io/react-md-spinner/
11 |
12 | ## Table of Contents
13 |
14 | - [Installation](#installation)
15 | - [Features](#features)
16 | - [Getting Started](#getting-started)
17 | - [Basic Usage](#basic-usage)
18 | - [Server-Side Rendering](#server-side-rendering)
19 | - [Example](#example)
20 | - [Props](#props)
21 | - [`size`](#size)
22 | - [`borderSize`](#bordersize)
23 | - [`duration`](#duration)
24 | - [`color1`](#color1)
25 | - [`color2`](#color2)
26 | - [`color3`](#color3)
27 | - [`color4`](#color4)
28 | - [`singleColor`](#singlecolor)
29 | - [API](#api)
30 | - [`ssrBehavior`](#ssrbehavior)
31 | - [As string output](#as-string-output)
32 | - [As React Components](#as-react-components)
33 | - [ChangeLog](#changelog)
34 | - [Contributing](#contributing)
35 | - [Available Scripts](#available-scripts)
36 | - [`yarn test`](#yarn-test)
37 | - [`yarn lint`](#yarn-lint)
38 | - [`yarn format`](#yarn-format)
39 | - [`yarn build`](#yarn-build)
40 | - [`yarn storybook`](#yarn-storybook)
41 | - [License](#license)
42 |
43 | ## Installation
44 |
45 | You can install the [react-md-spinner](https://www.npmjs.com/package/react-md-spinner) from [npm](https://www.npmjs.com/).
46 |
47 | ```bash
48 | $ npm i -S react-md-spinner
49 | # or
50 | $ yarn add react-md-spinner
51 | ```
52 |
53 | ## Features
54 |
55 | - :rocket: You can start using with zero configuration!
56 | - :wrench: Support to change of color, size, border and animation speed.
57 | - :sparkling_heart: It can also be used in single color.
58 | - :globe_with_meridians: Support Server-Side Rendering.
59 |
60 | ## Getting Started
61 |
62 | ### Basic Usage
63 |
64 | Because it is made of 100% inline styles, you can start using it right away without setting.
65 |
66 | ```typescript
67 | import React from "react";
68 | import MDSpinner from "react-md-spinner";
69 |
70 | export const SpinnerExample: React.FC = () => (
71 |
72 |
73 |
74 | );
75 | ```
76 |
77 | ### Server-Side Rendering
78 |
79 | The following is an example of Server-Side Rendering.
80 | Please checkout [examples](./examples/) directory for details.
81 |
82 | The point is to use `ssrBehavior`.
83 |
84 | #### Example
85 |
86 | **Note:** The following is pseudo code.
87 |
88 | **Client-Side:**
89 |
90 | ```typescript
91 | import React from "react";
92 | import { render } from "react-dom";
93 | import App from "./App";
94 |
95 | render(, document.getElementById("app"));
96 | ```
97 |
98 | **Server-Side:**
99 |
100 | ```typescript
101 | import { ssrBehavior } from "react-md-spinner";
102 |
103 | // ...
104 |
105 | const html = (root: JSX.Element) => `
106 |
107 |
108 |
109 | ${ssrBehavior.getStylesheetString()}
110 |
111 |
112 | ${renderToString(root)}
113 |
114 |
115 | `;
116 |
117 | app.get("/", (_req, res) => {
118 | res.status(200).send(`${renderer()}`);
119 | });
120 | ```
121 |
122 | **App:**
123 |
124 | ```typescript
125 | import React from "react";
126 | import MDSpinner from "react-md-spinner";
127 |
128 | export const App: React.FC = () => (
129 |
130 |
131 |
132 | );
133 | ```
134 |
135 | ## Props
136 |
137 | You can use the following Props. All Props are Optional!
138 |
139 | ### `size`
140 |
141 | **type:** `number`
142 | **default:** `28`
143 |
144 | Set the size (diameter) of the spinner circle.
145 |
146 | ### `borderSize`
147 |
148 | **type:** `number`
149 | **default:** `undefined`
150 |
151 | Set the spinner border size of. By default, the appropriate size is calculated according to the value of `size`.
152 |
153 | ### `duration`
154 |
155 | **type:** `number`
156 | **default:** `1333`
157 |
158 | Set the animation duration (ms) of the spinner.
159 |
160 | ### `color1`
161 |
162 | **type:** `string`
163 | **default:** !`rgb(66, 165, 245)`
164 |
165 | The color of the spinner. Can be set to any valid CSS string (hex, rgb, rgba).
166 |
167 | ### `color2`
168 |
169 | **type:** `string`
170 | **default:** `rgb(239, 83, 80)`
171 |
172 | Same as above.
173 |
174 | ### `color3`
175 |
176 | **type:** `string`
177 | **default:** `rgb(253, 216, 53)`
178 |
179 | Same as above.
180 |
181 | ### `color4`
182 |
183 | **type:** `string`
184 | **default:** `rgb(76, 175, 80)`
185 |
186 | Same as above.
187 |
188 | ### `singleColor`
189 |
190 | **type:** `string`
191 | **default:** `undefined`
192 |
193 | Same as above. Use this if the spinner should be in only one single color. The settings (props) for `color1` ~ `4` will be ignored by setting this `singleColor` property.
194 |
195 | ## API
196 |
197 | ### `ssrBehavior`
198 |
199 | In Server-Side Rendering you need to inject `@keyframes` inside the ``.
200 | `react-md-spinner` provides utilities to handle them.
201 |
202 | - `ssrBehavior.getStylesheetString(): string`
203 | - `ssrBehavior.getStylesheetComponent(): React.ReactNode`
204 |
205 | #### As string output
206 |
207 | ```typescript
208 | import { ssrBehavior } from "react-md-spinner";
209 |
210 | const html = () => `
211 |
212 | ${ssrBehavior.getStylesheetString()}
213 |
214 |
215 |
216 | // React stuff here
217 |
218 |
219 | `;
220 | ```
221 |
222 | #### As React Components
223 |
224 | ```typescript
225 | import React from "react";
226 | import { ssrBehavior } from "react-md-spinner";
227 |
228 | const Html: React.FC = () => (
229 |
230 | {ssrBehavior.getStylesheetComponent()}
231 |
232 | {/* React stuff here */}
233 |
234 |
235 | );
236 | ```
237 |
238 | ## ChangeLog
239 |
240 | See [CHANGELOG.md](./CHANGELOG.md)
241 |
242 | ## Contributing
243 |
244 | We are always welcoming your contribution :clap:
245 |
246 | 1. Fork (https://github.com/tsuyoshiwada/react-md-spinner) :tada:
247 | 1. Create a feature branch :coffee:
248 | 1. Run test suite with the `$ yarn test` command and confirm that it passes :zap:
249 | 1. Commit your changes :memo:
250 | 1. Rebase your local changes against the `master` branch :bulb:
251 | 1. Create new Pull Request :love_letter:
252 |
253 | ## Available Scripts
254 |
255 | ### `yarn test`
256 |
257 | Run unit test using Jest.
258 |
259 | ### `yarn lint`
260 |
261 | Run Lint of source code using ESLint.
262 |
263 | ### `yarn format`
264 |
265 | Run formatting using Prettier and ESLint's Fixer.
266 |
267 | ### `yarn build`
268 |
269 | Run build of TypeScript code.
270 |
271 | ### `yarn storybook`
272 |
273 | Run Storybook.
274 |
275 | ## License
276 |
277 | [MIT © tsuyoshiwada](./LICENSE)
278 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | pool:
2 | vmImage: 'ubuntu-latest'
3 |
4 | steps:
5 | - task: NodeTool@0
6 | inputs:
7 | versionSpec: '10.x'
8 | displayName: 'Install Node.js'
9 |
10 | - script: |
11 | yarn --pure-lockfile
12 | yarn test
13 | yarn lint
14 | yarn format:prettier && git diff --exit-code || exit 1
15 | displayName: 'yarn install and build'
16 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const { NODE_ENV: env } = process.env;
2 |
3 | const presets = [
4 | [
5 | "@babel/preset-env",
6 | {
7 | targets: "> 0.25%, last 2 versions, not ie < 11",
8 | modules: env === "test" ? "commonjs" : false
9 | }
10 | ],
11 | "@babel/preset-react",
12 | "@babel/preset-typescript"
13 | ];
14 |
15 | const plugins = ["@babel/plugin-proposal-class-properties", "macros"];
16 |
17 | module.exports = {
18 | presets,
19 | plugins
20 | };
21 |
--------------------------------------------------------------------------------
/docs/images/repo-banner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wadackel/react-md-spinner/1242f3fc797822e11894f7092dd6e94acd25d200/docs/images/repo-banner.gif
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | The following example can be run locally.
4 |
5 | - [Server-Side Rendering](./server-side-rendering/)
6 |
--------------------------------------------------------------------------------
/examples/server-side-rendering/.gitignore:
--------------------------------------------------------------------------------
1 | /dist
2 |
--------------------------------------------------------------------------------
/examples/server-side-rendering/README.md:
--------------------------------------------------------------------------------
1 | # Example: Server-Side rendering
2 |
3 | An example of spinner in Server-Side Rendering.
4 |
5 | 
6 |
7 | ## Example
8 |
9 | The point is to use `ssrBehavior`.
10 |
11 | ```javascript
12 | import { ssrBehavior } from "react-md-spinner";
13 |
14 | const html = (root: JSX.Element) => `
15 |
16 |
17 |
18 | ${ssrBehavior.getStylesheetString()}
19 |
20 |
21 | ${renderToString(root)}
22 |
23 |
24 | `;
25 |
26 | app.get("/", (_req, res) => {
27 | res.status(200).send(`${renderer()}`);
28 | });
29 | ```
30 |
31 | ### App
32 |
33 | ```javascript
34 | import React, { useState, useEffect } from "react";
35 | import MDSpinner from "react-md-spinner";
36 |
37 | export const App: React.FC = () => {
38 | const [mounted, setMounted] = useState(false);
39 |
40 | useEffect(() => {
41 | setMounted(true);
42 | }, []);
43 |
44 | return (
45 |
46 |
{mounted ? "Mounted" : "Loading..."}
47 |
48 |
49 | );
50 | };
51 | ```
52 |
53 | ## Available Scripts
54 |
55 | In the project directory, you can run:
56 |
57 | ### `$ npm start`
58 |
59 | Runs the app.
60 | Open http://localhost:8080 to view it in the browser.
61 |
62 | ### `$ npm run build`
63 |
64 | Build the client side and server side code.
65 |
--------------------------------------------------------------------------------
/examples/server-side-rendering/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require("../../babel.config");
2 |
--------------------------------------------------------------------------------
/examples/server-side-rendering/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "react-md-spinner-server-side-rendering",
4 | "scripts": {
5 | "serve": "nodemon dist/server.js",
6 | "build": "webpack",
7 | "clean": "rimraf dist",
8 | "postinstall": "yarn build",
9 | "prebuild": "yarn clean"
10 | },
11 | "author": "tsuyoshiwada",
12 | "license": "MIT",
13 | "devDependencies": {
14 | "@babel/core": "^7.4.5",
15 | "@babel/preset-env": "^7.4.5",
16 | "@babel/preset-typescript": "^7.3.3",
17 | "@types/express": "^4.16.1",
18 | "@types/react": "^16.8.19",
19 | "@types/react-dom": "^16.8.4",
20 | "babel-loader": "^8.0.6",
21 | "nodemon": "^1.19.1",
22 | "rimraf": "^2.6.3",
23 | "typescript": "^3.5.1",
24 | "webpack": "^4.32.2",
25 | "webpack-cli": "^3.3.2",
26 | "webpack-node-externals": "^1.7.2"
27 | },
28 | "dependencies": {
29 | "express": "^4.17.1",
30 | "react": "^16.8.6",
31 | "react-dom": "^16.8.6"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/examples/server-side-rendering/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wadackel/react-md-spinner/1242f3fc797822e11894f7092dd6e94acd25d200/examples/server-side-rendering/screenshot.gif
--------------------------------------------------------------------------------
/examples/server-side-rendering/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import MDSpinner from "../../../src";
3 |
4 | export const App: React.FC = () => {
5 | const [mounted, setMounted] = useState(false);
6 |
7 | useEffect(() => {
8 | setMounted(true);
9 | }, []);
10 |
11 | return (
12 |
13 |
{mounted ? "Mounted" : "Loading..."}
14 |
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/examples/server-side-rendering/src/client/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "react-dom";
3 | import { App } from "../App";
4 |
5 | render(, document.getElementById("app"));
6 |
--------------------------------------------------------------------------------
/examples/server-side-rendering/src/server/index.tsx:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 | import express from "express";
3 | import React from "react";
4 | import { renderToString } from "react-dom/server";
5 | import { ssrBehavior } from "../../../../src";
6 | import { App } from "../App";
7 |
8 | // You can try out the output of the component if setting it to `true`.
9 | const USE_COMPONENT = false;
10 |
11 | const PORT = 8080;
12 |
13 | // for rendering as string.
14 | const html = (root: JSX.Element) => `
15 |
16 |
17 |
18 | ${ssrBehavior.getStylesheetString()}
19 |
20 |
21 | ${renderToString(root)}
22 |
23 |
24 | `;
25 |
26 | // for rendering as component.
27 | const Html = (root: JSX.Element) =>
28 | renderToString(
29 |
30 |
31 |
32 |
33 | {ssrBehavior.getStylesheetComponent()}
34 |
35 |
36 | {root}
37 |
38 |
39 |
40 | );
41 |
42 | // React with Express Server-Side rendering example.
43 | const app = express();
44 |
45 | app.use(express.static(path.join(__dirname, "public")));
46 |
47 | app.get("/", (_req, res) => {
48 | const renderer = USE_COMPONENT ? Html : html;
49 |
50 | res.status(200).send(`${renderer()}`);
51 | });
52 |
53 | app.listen(PORT, () => {
54 | // eslint-disable-next-line no-console
55 | console.log(`ReactMDSpinner SSR Example: Listening on port ${PORT}`);
56 | });
57 |
--------------------------------------------------------------------------------
/examples/server-side-rendering/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const nodeExternals = require("webpack-node-externals");
3 |
4 | const common = {
5 | mode: "development",
6 | resolve: {
7 | extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
8 | modules: [path.join(__dirname, "src"), "node_modules"]
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.(ts|tsx)$/,
14 | exclude: /node_modules/,
15 | use: [{ loader: "babel-loader" }]
16 | }
17 | ]
18 | }
19 | };
20 |
21 | module.exports = [
22 | {
23 | ...common,
24 | target: "web",
25 | entry: path.join(__dirname, "src", "client", "index.tsx"),
26 | output: {
27 | path: path.join(__dirname, "dist", "public"),
28 | filename: "client.js"
29 | }
30 | },
31 | {
32 | ...common,
33 | target: "node",
34 | entry: path.join(__dirname, "src", "server", "index.tsx"),
35 | output: {
36 | path: path.join(__dirname, "dist"),
37 | filename: "server.js"
38 | },
39 | externals: nodeExternals(),
40 | node: {
41 | __dirname: false,
42 | __filename: false
43 | }
44 | }
45 | ];
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-md-spinner",
3 | "version": "1.0.0",
4 | "description": "Material Design Spinner components for React.js.",
5 | "main": "dist/react-md-spinner.cjs.js",
6 | "module": "dist/react-md-spinner.esm.js",
7 | "types": "dist/index.d.ts",
8 | "scripts": {
9 | "build": "npm-run-all -s clean -p build:js build:types",
10 | "build:js": "rollup -c",
11 | "build:types": "tsc -p .",
12 | "test": "jest",
13 | "test:watch": "yarn test --watch",
14 | "lint": "eslint \"**/*.+(ts|tsx)\"",
15 | "format": "npm-run-all format:prettier format:eslint",
16 | "format:prettier": "prettier --write \"**/*.+(ts|tsx|js|md|json)\"",
17 | "format:eslint": "yarn lint --fix",
18 | "clean": "rimraf dist storybook-static",
19 | "storybook": "start-storybook -p 3000",
20 | "storybook:build": "build-storybook",
21 | "release": "np",
22 | "postrelease": "npm-run-all storybook:build deploy",
23 | "deploy": "gh-pages -d storybook-static"
24 | },
25 | "keywords": [
26 | "react",
27 | "react-component",
28 | "material design",
29 | "spinner",
30 | "loader"
31 | ],
32 | "files": [
33 | "docs",
34 | "dist",
35 | "LICENSE"
36 | ],
37 | "repository": {
38 | "type": "git",
39 | "url": "git+https://github.com/tsuyoshiwada/react-md-spinner.git"
40 | },
41 | "author": "tsuyoshiwada",
42 | "license": "MIT",
43 | "bugs": {
44 | "url": "https://github.com/tsuyoshiwada/react-md-spinner/issues"
45 | },
46 | "homepage": "https://github.com/tsuyoshiwada/react-md-spinner#readme",
47 | "peerDependencies": {
48 | "react": ">=16.3.0",
49 | "react-dom": ">=16.3.0"
50 | },
51 | "devDependencies": {
52 | "@babel/cli": "^7.4.4",
53 | "@babel/core": "^7.4.5",
54 | "@babel/plugin-proposal-class-properties": "^7.4.4",
55 | "@babel/preset-env": "^7.4.5",
56 | "@babel/preset-react": "^7.0.0",
57 | "@babel/preset-typescript": "^7.3.3",
58 | "@storybook/addon-actions": "^5.1.3",
59 | "@storybook/addon-links": "^5.1.3",
60 | "@storybook/addon-storyshots": "^5.1.3",
61 | "@storybook/addon-storysource": "^5.1.3",
62 | "@storybook/addons": "^5.1.3",
63 | "@storybook/react": "^5.1.3",
64 | "@testing-library/react": "^8.0.1",
65 | "@types/jest": "^24.0.13",
66 | "@types/react": "^16.8.19",
67 | "@types/react-dom": "^16.8.4",
68 | "@types/storybook__addon-actions": "^3.4.3",
69 | "@types/storybook__react": "^4.0.2",
70 | "@typescript-eslint/eslint-plugin": "^1.9.0",
71 | "babel-loader": "^8.0.6",
72 | "babel-plugin-macros": "^2.6.1",
73 | "browser-sync": "^2.26.7",
74 | "eslint": "^5.16.0",
75 | "eslint-config-prettier": "^4.3.0",
76 | "eslint-plugin-prettier": "^3.1.0",
77 | "eslint-plugin-react": "^7.13.0",
78 | "gh-pages": "^2.0.1",
79 | "husky": "^2.4.0",
80 | "jest": "^24.8.0",
81 | "jest-dom": "^3.5.0",
82 | "lint-staged": "^8.2.0",
83 | "np": "^5.0.3",
84 | "npm-run-all": "^4.1.5",
85 | "prettier": "^1.18.2",
86 | "react": "^16.8.6",
87 | "react-dom": "^16.8.6",
88 | "react-test-renderer": "^16.8.6",
89 | "require-context.macro": "^1.0.4",
90 | "rimraf": "^2.6.3",
91 | "rollup": "^1.14.4",
92 | "rollup-plugin-babel": "^4.3.2",
93 | "rollup-plugin-node-resolve": "^5.0.1",
94 | "typescript": "^3.5.1"
95 | },
96 | "dependencies": {
97 | "stylis": "^3.5.4"
98 | },
99 | "husky": {
100 | "hooks": {
101 | "pre-commit": "lint-staged"
102 | }
103 | },
104 | "lint-staged": {
105 | "*.{ts,tsx,js,json,css,md}": [
106 | "prettier --write",
107 | "git add"
108 | ]
109 | },
110 | "jest": {
111 | "roots": [
112 | "test"
113 | ]
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from "rollup-plugin-babel";
2 | import resolve from "rollup-plugin-node-resolve";
3 | import pkg from "./package.json";
4 |
5 | const banner = `/*! @preserve ${pkg.name} v${pkg.version} - ${pkg.author} | ${pkg.license} license. */`;
6 |
7 | export default {
8 | input: "src/index.ts",
9 | external: [
10 | ...Object.keys(pkg.peerDependencies),
11 | ...Object.keys(pkg.dependencies)
12 | ],
13 | output: [
14 | {
15 | banner,
16 | file: pkg.main,
17 | format: "cjs"
18 | },
19 | {
20 | banner,
21 | file: pkg.module,
22 | format: "esm"
23 | }
24 | ],
25 | plugins: [
26 | resolve({
27 | extensions: [".ts", ".tsx"]
28 | }),
29 | babel({
30 | exclude: "node_modules/**",
31 | runtimeHelpers: true,
32 | extensions: [".ts", ".tsx"]
33 | })
34 | ]
35 | };
36 |
--------------------------------------------------------------------------------
/src/MDSpinner.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { getStyles } from "./styles/get-styles";
3 | import { keyframes } from "./styles/keyframes";
4 | import { injectStyles, uninjectStyles } from "./utils/manipulation";
5 | import { STYLE_DATA_NAME } from "./constants";
6 |
7 | export type Props = React.ComponentProps<"span"> & {
8 | singleColor?: string;
9 | size?: number;
10 | borderSize?: number;
11 | duration?: number;
12 | color1?: string;
13 | color2?: string;
14 | color3?: string;
15 | color4?: string;
16 | };
17 |
18 | export class MDSpinner extends React.PureComponent {
19 | public static defaultProps = {
20 | size: 28,
21 | duration: 1333,
22 | color1: "rgb(66, 165, 245)",
23 | color2: "rgb(239, 83, 80)",
24 | color3: "rgb(253, 216, 53)",
25 | color4: "rgb(76, 175, 80)"
26 | };
27 |
28 | private static mountedInstanceCount = 0;
29 |
30 | public componentDidMount() {
31 | if (MDSpinner.mountedInstanceCount < 1) {
32 | injectStyles(STYLE_DATA_NAME, keyframes);
33 | }
34 |
35 | MDSpinner.mountedInstanceCount++;
36 | }
37 |
38 | public componentWillUnmount() {
39 | MDSpinner.mountedInstanceCount = Math.max(
40 | 0,
41 | MDSpinner.mountedInstanceCount - 1
42 | );
43 |
44 | if (MDSpinner.mountedInstanceCount < 1) {
45 | uninjectStyles(STYLE_DATA_NAME);
46 | }
47 | }
48 |
49 | public render() {
50 | const {
51 | singleColor: _singleColor,
52 | size: _size,
53 | borderSize: _borderSize,
54 | duration: _duration,
55 | color1: _color1,
56 | color2: _color2,
57 | color3: _color3,
58 | color4: _color4,
59 | ...rest
60 | } = this.props;
61 |
62 | const {
63 | rootStyle,
64 | layerStyles,
65 | layerAfterStyle,
66 | clipStyle,
67 | clip1AfterStyles,
68 | clip2AfterStyles
69 | } = getStyles(this.props);
70 |
71 | const layers = [];
72 |
73 | for (let i = 0; i < 4; i++) {
74 | layers.push(
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | );
85 | }
86 |
87 | return (
88 |
95 | {layers}
96 |
97 | );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const STYLE_DATA_NAME = "react-md-spinner";
2 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as ssrBehavior from "./ssr-behavior";
2 | import { MDSpinner } from "./MDSpinner";
3 |
4 | export { ssrBehavior };
5 | export default MDSpinner;
6 |
--------------------------------------------------------------------------------
/src/ssr-behavior.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { STYLE_DATA_NAME } from "./constants";
3 | import { keyframes } from "./styles/keyframes";
4 |
5 | export const getStylesheetString = (): string =>
6 | ``;
7 |
8 | export const getStylesheetComponent = (): React.ReactNode =>
9 | React.createElement("style", {
10 | type: "text/css",
11 | [`data-${STYLE_DATA_NAME}`]: "",
12 | dangerouslySetInnerHTML: {
13 | __html: keyframes
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/src/styles/get-styles.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import { Props } from "../MDSpinner";
3 | import { Keyframe } from "./keyframes";
4 |
5 | type StyleProps = Pick<
6 | Props,
7 | | "size"
8 | | "duration"
9 | | "borderSize"
10 | | "singleColor"
11 | | "color1"
12 | | "color2"
13 | | "color3"
14 | | "color4"
15 | >;
16 |
17 | const getColors = (props: StyleProps) => {
18 | const { singleColor, color1, color2, color3, color4 } = props;
19 |
20 | return singleColor
21 | ? [singleColor, singleColor, singleColor, singleColor]
22 | : [color1, color2, color3, color4];
23 | };
24 |
25 | export const getStyles = (props: StyleProps) => {
26 | const size = props.size as number;
27 | const duration = props.duration as number;
28 | const borderSize = props.borderSize as number;
29 |
30 | const borderWidth = borderSize || Math.max(1, Math.round(size * 0.107142));
31 | const colors = getColors(props);
32 | const arcSize = 270;
33 | const arcStartRotate = 216;
34 | const rootDuration = (360 * duration) / (arcStartRotate + (360 - arcSize));
35 |
36 | const rootStyle: CSSProperties = {
37 | display: "inline-block",
38 | position: "relative",
39 | width: size,
40 | height: size,
41 | verticalAlign: "middle",
42 | fontSize: "0",
43 | animation: `${Keyframe.ROOT_ROTATE} ${rootDuration}ms linear infinite`,
44 | WebkitAnimation: `${Keyframe.ROOT_ROTATE} ${rootDuration}ms linear infinite`
45 | };
46 |
47 | const layerStyles: CSSProperties[] = colors.map((color, i) => ({
48 | boxSizing: "border-box",
49 | display: "block",
50 | position: "absolute",
51 | width: "100%",
52 | height: "100%",
53 | borderColor: color,
54 | whiteSpace: "nowrap",
55 | opacity: 1,
56 | animationName: `${Keyframe.FILL_UNFILL_ROTATE}, ${
57 | Keyframe[`LAYER_${i + 1}_FADE_IN_OUT` as keyof typeof Keyframe]
58 | }`,
59 | WebkitAnimationName: `${Keyframe.FILL_UNFILL_ROTATE}, ${
60 | Keyframe[`LAYER_${i + 1}_FADE_IN_OUT` as keyof typeof Keyframe]
61 | }`,
62 | animationDuration: `${duration * colors.length}ms`,
63 | WebkitAnimationDuration: `${duration * colors.length}ms`,
64 | animationTimingFunction: "cubic-bezier(.4, 0, .2, 1)",
65 | WebkitAnimationTimingFunction: "cubic-bezier(.4, 0, .2, 1)",
66 | animationIterationCount: "infinite",
67 | WebkitAnimationIterationCount: "infinite"
68 | }));
69 |
70 | const clipStyle: CSSProperties = {
71 | display: "inline-block",
72 | boxSizing: "border-box",
73 | position: "relative",
74 | width: "50%",
75 | height: "100%",
76 | overflow: "hidden",
77 | borderColor: "inherit"
78 | };
79 |
80 | const layerClipAfterStyle: CSSProperties = {
81 | display: "inline-block",
82 | boxSizing: "border-box",
83 | position: "absolute",
84 | top: 0,
85 | borderRadius: "50%"
86 | };
87 |
88 | const layerAfterStyle: CSSProperties = {
89 | ...layerClipAfterStyle,
90 | left: "45%",
91 | width: "10%",
92 | borderWidth,
93 | borderColor: "inherit",
94 | borderTopStyle: "solid"
95 | };
96 |
97 | const clipAfterStyle: CSSProperties = {
98 | ...layerClipAfterStyle,
99 | bottom: 0,
100 | width: "200%",
101 | borderWidth,
102 | borderStyle: "solid",
103 | animationDuration: `${duration}ms`,
104 | WebkitAnimationDuration: `${duration}ms`,
105 | animationTimingFunction: "cubic-bezier(.4, 0, .2, 1)",
106 | WebkitAnimationTimingFunction: "cubic-bezier(.4, 0, .2, 1)",
107 | animationIterationCount: "infinite",
108 | WebkitAnimationIterationCount: "infinite"
109 | };
110 |
111 | const clip1AfterStyle: CSSProperties = {
112 | ...clipAfterStyle,
113 | left: 0,
114 | transform: "rotate(129deg)",
115 | WebkitTransform: "rotate(129deg)",
116 | animationName: Keyframe.LEFT_SPIN,
117 | WebkitAnimationName: Keyframe.LEFT_SPIN
118 | };
119 |
120 | const clip1AfterStyles: CSSProperties[] = colors.map(color => ({
121 | ...clip1AfterStyle,
122 | borderColor: `${color} transparent transparent ${color}`
123 | }));
124 |
125 | const clip2AfterStyle: CSSProperties = {
126 | ...clipAfterStyle,
127 | left: "-100%",
128 | transform: "rotate(-129deg)",
129 | WebkitTransform: "rotate(-129deg)",
130 | animationName: Keyframe.RIGHT_SPIN,
131 | WebkitAnimationName: Keyframe.RIGHT_SPIN
132 | };
133 |
134 | const clip2AfterStyles: CSSProperties[] = colors.map(color => ({
135 | ...clip2AfterStyle,
136 | borderColor: `${color} ${color} transparent transparent`
137 | }));
138 |
139 | return {
140 | rootStyle,
141 | layerStyles,
142 | layerAfterStyle,
143 | clipStyle,
144 | clip1AfterStyles,
145 | clip2AfterStyles
146 | };
147 | };
148 |
--------------------------------------------------------------------------------
/src/styles/keyframes.ts:
--------------------------------------------------------------------------------
1 | import Stylis from "stylis";
2 |
3 | const KEYFRAME_PREFIX = "__react-md-spinner-animation__";
4 |
5 | const stylis = new Stylis({
6 | global: false,
7 | cascade: true,
8 | keyframe: true,
9 | prefix: true,
10 | compress: false
11 | });
12 |
13 | export const Keyframe = {
14 | ROOT_ROTATE: `${KEYFRAME_PREFIX}root-rotate`,
15 | FILL_UNFILL_ROTATE: `${KEYFRAME_PREFIX}fill-unfill-rotate`,
16 | LAYER_1_FADE_IN_OUT: `${KEYFRAME_PREFIX}layer-1-fade-in-out`,
17 | LAYER_2_FADE_IN_OUT: `${KEYFRAME_PREFIX}layer-2-fade-in-out`,
18 | LAYER_3_FADE_IN_OUT: `${KEYFRAME_PREFIX}layer-3-fade-in-out`,
19 | LAYER_4_FADE_IN_OUT: `${KEYFRAME_PREFIX}layer-4-fade-in-out`,
20 | LEFT_SPIN: `${KEYFRAME_PREFIX}left-spin`,
21 | RIGHT_SPIN: `${KEYFRAME_PREFIX}right-spin`
22 | };
23 |
24 | export const keyframes = stylis(
25 | "",
26 | `
27 | @keyframes ${Keyframe.ROOT_ROTATE} {
28 | to { transform: rotate(360deg); }
29 | }
30 |
31 | @keyframes ${Keyframe.FILL_UNFILL_ROTATE} {
32 | 12.5% { transform: rotate(135deg) }
33 | 25% { transform: rotate(270deg) }
34 | 37.5% { transform: rotate(405deg) }
35 | 50% { transform: rotate(540deg) }
36 | 62.5% { transform: rotate(675deg) }
37 | 75% { transform: rotate(810deg) }
38 | 87.5% { transform: rotate(945deg) }
39 | 100% { transform: rotate(1080deg) }
40 | }
41 |
42 | @keyframes ${Keyframe.LAYER_1_FADE_IN_OUT} {
43 | 0% { opacity: 1 }
44 | 25% { opacity: 1 }
45 | 26% { opacity: 0 }
46 | 89% { opacity: 0 }
47 | 90% { opacity: 1 }
48 | 100% { opacity: 1 }
49 | }
50 |
51 | @keyframes ${Keyframe.LAYER_2_FADE_IN_OUT} {
52 | 0% { opacity: 0 }
53 | 15% { opacity: 0 }
54 | 25% { opacity: 1 }
55 | 50% { opacity: 1 }
56 | 51% { opacity: 0 }
57 | 100% { opacity: 0 }
58 | }
59 |
60 | @keyframes ${Keyframe.LAYER_3_FADE_IN_OUT} {
61 | 0% { opacity: 0 }
62 | 40% { opacity: 0 }
63 | 50% { opacity: 1 }
64 | 75% { opacity: 1 }
65 | 76% { opacity: 0 }
66 | 100% { opacity: 0 }
67 | }
68 |
69 | @keyframes ${Keyframe.LAYER_4_FADE_IN_OUT} {
70 | 0% { opacity: 0 }
71 | 65% { opacity: 0 }
72 | 75% { opacity: 1 }
73 | 90% { opacity: 1 }
74 | 100% { opacity: 0 }
75 | }
76 |
77 | @keyframes ${Keyframe.LEFT_SPIN} {
78 | 0% { transform: rotate(130deg) }
79 | 50% { transform: rotate(-5deg) }
80 | 100% { transform: rotate(130deg) }
81 | }
82 |
83 | @keyframes ${Keyframe.RIGHT_SPIN} {
84 | 0% { transform: rotate(-130deg) }
85 | 50% { transform: rotate(5deg) }
86 | 100% { transform: rotate(-130deg) }
87 | }
88 | `
89 | );
90 |
--------------------------------------------------------------------------------
/src/utils/manipulation.ts:
--------------------------------------------------------------------------------
1 | export const injectStyles = (name: string, rules: string) => {
2 | const el = document.createElement("style");
3 | el.type = "text/css";
4 | el.setAttribute(`data-${name}`, "");
5 | el.innerHTML = rules;
6 |
7 | document.head.appendChild(el);
8 | };
9 |
10 | export const uninjectStyles = (name: string) => {
11 | const el = document.querySelector(`[data-${name}]`);
12 | if (el != null && el.parentNode != null) {
13 | el.parentNode.removeChild(el);
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/stories/index.stories.tsx:
--------------------------------------------------------------------------------
1 | import { storiesOf } from "@storybook/react";
2 | import { action } from "@storybook/addon-actions";
3 | import React from "react";
4 | import MDSpinner from "../src";
5 |
6 | const space = ;
7 |
8 | storiesOf("MDSpinner", module)
9 | .add("with default", () => )
10 | .add("with pass basic props", () => (
11 |
17 | ))
18 | .add("with sizes", () => (
19 | <>
20 |
21 | {space}
22 |
23 | {space}
24 |
25 | {space}
26 |
27 | {space}
28 |
29 | >
30 | ))
31 | .add("with border sizes", () => (
32 | <>
33 |
34 | {space}
35 |
36 | {space}
37 |
38 | {space}
39 |
40 | {space}
41 |
42 | >
43 | ))
44 | .add("with durations", () => (
45 | <>
46 |
47 | {space}
48 |
49 | {space}
50 |
51 | >
52 | ))
53 | .add("with custom colors", () => (
54 |
60 | ))
61 | .add("with single color", () => );
62 |
--------------------------------------------------------------------------------
/test/MDSpinner.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, cleanup } from "@testing-library/react";
3 | import "jest-dom/extend-expect";
4 | import MDSpinner from "../src";
5 |
6 | describe("", () => {
7 | afterEach(cleanup);
8 |
9 | test("Shold be inject/uninject `@keyframes` when cDM/cWU triggered", () => {
10 | expect(document.head.children).toHaveLength(0);
11 |
12 | const { unmount: unmount1 } = render();
13 |
14 | expect(document.head.children).toHaveLength(1);
15 | expect(document.head.children[0]).toHaveAttribute("data-react-md-spinner");
16 |
17 | const { unmount: unmount2 } = render();
18 |
19 | expect(document.head.children).toHaveLength(1);
20 | expect(document.head.children[0]).toHaveAttribute("data-react-md-spinner");
21 |
22 | unmount1();
23 |
24 | expect(document.head.children).toHaveLength(1);
25 | expect(document.head.children[0]).toHaveAttribute("data-react-md-spinner");
26 |
27 | unmount2();
28 |
29 | expect(document.head.children).toHaveLength(0);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/test/storyshots.test.js:
--------------------------------------------------------------------------------
1 | import initStoryshots, {
2 | multiSnapshotWithOptions
3 | } from "@storybook/addon-storyshots";
4 |
5 | initStoryshots({
6 | test: multiSnapshotWithOptions({})
7 | });
8 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "ESNEXT",
5 | "module": "ESNext",
6 | "lib": ["esnext", "dom"],
7 | "jsx": "react",
8 | "allowJs": false,
9 | "checkJs": false,
10 | "declaration": true,
11 | "declarationMap": false,
12 | "sourceMap": false,
13 | "rootDirs": ["src", "stories"],
14 | "emitDeclarationOnly": true,
15 | "importHelpers": false,
16 | "downlevelIteration": false,
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noImplicitReturns": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "moduleResolution": "node",
23 | "allowSyntheticDefaultImports": true,
24 | "esModuleInterop": true
25 | },
26 | "include": ["src/**/*"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------