├── .github └── workflows │ ├── main.yml │ └── size.yml ├── .gitignore ├── .storybook ├── main.js └── preview.js ├── LICENSE ├── README.md ├── package.json ├── src ├── BarWave.tsx ├── BouncingBalls.tsx ├── CircularProgress.tsx ├── Coin.tsx ├── FadingBalls.tsx ├── FadingDots.tsx ├── FillingBottle.tsx ├── FlippingSquare.tsx ├── Hypnosis.tsx ├── Messaging.tsx ├── Ring.tsx ├── Spin.tsx ├── SpinStretch.tsx ├── TwinSpin.tsx └── index.tsx ├── stories ├── BarWave.stories.tsx ├── BouncingBalls.stories.tsx ├── CircularProgress.stories.tsx ├── Coin.stories.tsx ├── FadingBalls.stories.tsx ├── FadingDots.stories.tsx ├── FillingBottle.stories.tsx ├── FlippingSquare.stories.tsx ├── Hypnosis.stories.tsx ├── Messaging.stories.tsx ├── Ring.stories.tsx ├── Spin.stories.tsx ├── SpinStretch.stories.tsx └── TwinSpin.stories.tsx ├── test └── index.test.tsx ├── tsconfig.json └── yarn.lock /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ["14.x", "16.x"] 11 | os: [ubuntu-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../stories/**/*.stories.@(ts|tsx|js|jsx)'], 3 | addons: ['@storybook/addon-links', '@storybook/addon-essentials'], 4 | // https://storybook.js.org/docs/react/configure/typescript#mainjs-configuration 5 | typescript: { 6 | check: true, // type-check stories during Storybook build 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | // https://storybook.js.org/docs/react/writing-stories/parameters#global-parameters 2 | export const parameters = { 3 | // https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args 4 | actions: { argTypesRegex: '^on.*' }, 5 | }; 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 napthedev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React CSSFx Loading 2 | 3 | ## React wrapper for the CSSFx collection (loading animation only) 4 | 5 | Original Design and Code: [https://cssfx.netlify.app/](https://cssfx.netlify.app/) 6 | 7 | ## Preview 8 | 9 | ![Preview](https://res.cloudinary.com/naptest/image/upload/v1634719726/cssfx_ecuj37.gif) 10 | 11 | ## Live demo 12 | 13 | Demo: [https://react-cssfx.surge.sh/](https://react-cssfx.surge.sh/) 14 | Storybook: [https://cssfx-storybook.vercel.app/](https://cssfx-storybook.vercel.app/) 15 | 16 | ## Usage 17 | 18 | ```javascript 19 | // Import Components 20 | 21 | // Tree shakable 22 | import { BarWave } from "react-cssfx-loading"; 23 | 24 | // Render 25 | 26 | 27 | // Available Props 28 | 29 | 30 | // It also supports all props of an HTML element (or JSX) 31 | alert("Clicked")} key="key" /> 32 | ``` 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.1.0", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "scripts": { 14 | "start": "tsdx watch", 15 | "build": "tsdx build", 16 | "test": "tsdx test --passWithNoTests", 17 | "lint": "tsdx lint", 18 | "prepare": "tsdx build", 19 | "size": "size-limit", 20 | "analyze": "size-limit --why", 21 | "storybook": "start-storybook -p 6006", 22 | "build-storybook": "build-storybook" 23 | }, 24 | "peerDependencies": { 25 | "react": ">=16" 26 | }, 27 | "husky": { 28 | "hooks": { 29 | "pre-commit": "tsdx lint" 30 | } 31 | }, 32 | "prettier": { 33 | "printWidth": 100, 34 | "semi": true, 35 | "singleQuote": false, 36 | "trailingComma": "es5" 37 | }, 38 | "name": "react-cssfx-loading", 39 | "description": "React wrapper for the CSSFX loading collection", 40 | "author": "napthedev", 41 | "module": "dist/react-cssfx-loading.esm.js", 42 | "size-limit": [ 43 | { 44 | "path": "dist/react-cssfx-loading.cjs.production.min.js", 45 | "limit": "20 KB" 46 | }, 47 | { 48 | "path": "dist/react-cssfx-loading.esm.js", 49 | "limit": "20 KB" 50 | } 51 | ], 52 | "devDependencies": { 53 | "@babel/core": "^7.18.6", 54 | "@size-limit/preset-small-lib": "^7.0.8", 55 | "@storybook/addon-essentials": "^6.5.9", 56 | "@storybook/addon-info": "^5.3.21", 57 | "@storybook/addon-links": "^6.5.9", 58 | "@storybook/addons": "^6.5.9", 59 | "@storybook/react": "^6.5.9", 60 | "@testing-library/react": "^13.3.0", 61 | "@types/react": "^18.0.14", 62 | "@types/react-dom": "^18.0.5", 63 | "babel-loader": "^8.2.5", 64 | "husky": "^8.0.1", 65 | "react": "^18.2.0", 66 | "react-dom": "^18.2.0", 67 | "react-is": "^18.2.0", 68 | "size-limit": "^7.0.8", 69 | "tsdx": "^0.14.1", 70 | "tslib": "^2.4.0", 71 | "typescript": "^4.7.4" 72 | }, 73 | "dependencies": { 74 | "goober": "^2.1.10" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/BarWave.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const grow = keyframes` 6 | 0%, 100% { 7 | transform: scaleY(1); 8 | } 9 | 10 | 50% { 11 | transform: scaleY(2); 12 | } 13 | `; 14 | 15 | export interface BarWaveProps { 16 | className?: string; 17 | color?: string; 18 | width?: number | string; 19 | height?: number | string; 20 | style?: React.CSSProperties; 21 | duration?: string; 22 | } 23 | 24 | const BarWave: React.FC> = ({ 25 | className = "", 26 | color = "#0d6efd", 27 | width = "2rem", 28 | height = "1rem", 29 | style = {}, 30 | duration = "1s", 31 | ...others 32 | }) => { 33 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 34 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 35 | 36 | return ( 37 |
72 | 73 | 74 | 75 | 76 |
77 | ); 78 | }; 79 | 80 | export default BarWave; 81 | -------------------------------------------------------------------------------- /src/BouncingBalls.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const leftSwing = keyframes` 6 | 50%, 7 | 100% { 8 | transform: translateX(0); 9 | } 10 | `; 11 | 12 | const rightSwing = keyframes` 13 | 50% { 14 | transform: translateX(0); 15 | } 16 | 100% { 17 | transform: translateX(100%); 18 | } 19 | `; 20 | 21 | export interface BouncingBallsProps { 22 | className?: string; 23 | color?: string; 24 | width?: number | string; 25 | height?: number | string; 26 | style?: React.CSSProperties; 27 | duration?: string; 28 | } 29 | 30 | const BouncingBalls: React.FC> = ({ 31 | className = "", 32 | color = "#0d6efd", 33 | width = "3rem", 34 | height = "1rem", 35 | style = {}, 36 | duration = "0.5s", 37 | ...others 38 | }) => { 39 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 40 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 41 | 42 | return ( 43 |
73 |
74 |
75 |
76 |
77 | ); 78 | }; 79 | 80 | export default BouncingBalls; 81 | -------------------------------------------------------------------------------- /src/CircularProgress.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const rotate = keyframes` 6 | 100% { 7 | transform: rotate(360deg); 8 | } 9 | `; 10 | 11 | const dash = keyframes` 12 | 0% { 13 | stroke-dasharray: 1, 200; 14 | stroke-dashoffset: 0; 15 | } 16 | 50% { 17 | stroke-dasharray: 90, 200; 18 | stroke-dashoffset: -35px; 19 | } 20 | 100% { 21 | stroke-dashoffset: -125px; 22 | } 23 | `; 24 | 25 | export interface CircularProgressProps { 26 | className?: string; 27 | color?: string; 28 | width?: number | string; 29 | height?: number | string; 30 | style?: React.CSSProperties; 31 | duration?: string; 32 | } 33 | 34 | const CircularProgress: React.FC> = ({ 35 | className = "", 36 | color = "#0d6efd", 37 | width = "3rem", 38 | height = "3rem", 39 | style = {}, 40 | duration = "2s", 41 | ...others 42 | }) => { 43 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 44 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 45 | 46 | return ( 47 | 61 | 75 | 76 | ); 77 | }; 78 | 79 | export default CircularProgress; 80 | -------------------------------------------------------------------------------- /src/Coin.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const spin = keyframes` 6 | to { 7 | transform: rotateY(540deg); 8 | } 9 | `; 10 | 11 | export interface CoinProps { 12 | className?: string; 13 | color?: string; 14 | width?: number | string; 15 | height?: number | string; 16 | style?: React.CSSProperties; 17 | duration?: string; 18 | } 19 | 20 | const Coin: React.FC> = ({ 21 | className = "", 22 | color = "#0d6efd", 23 | width = "2rem", 24 | height = "2rem", 25 | style = {}, 26 | duration = "1.2s", 27 | ...others 28 | }) => { 29 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 31 | 32 | return ( 33 |
42 |
51 |
52 | ); 53 | }; 54 | 55 | export default Coin; 56 | -------------------------------------------------------------------------------- /src/FadingBalls.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const fade = keyframes` 6 | from { 7 | opacity: 1; 8 | } 9 | to { 10 | opacity: 0; 11 | } 12 | `; 13 | 14 | export interface FadingBallsProps { 15 | className?: string; 16 | color?: string; 17 | width?: number | string; 18 | height?: number | string; 19 | style?: React.CSSProperties; 20 | duration?: string; 21 | } 22 | 23 | const FadingBalls: React.FC> = ({ 24 | className = "", 25 | color = "#0d6efd", 26 | width = "4rem", 27 | height = "1rem", 28 | style = {}, 29 | duration = "0.8s", 30 | ...others 31 | }) => { 32 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 33 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 34 | 35 | return ( 36 |
65 |
66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | export default FadingBalls; 73 | -------------------------------------------------------------------------------- /src/FadingDots.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const fade = keyframes` 6 | to { 7 | opacity: 0.2; 8 | } 9 | `; 10 | 11 | export interface FadingDotsProps { 12 | className?: string; 13 | color?: string; 14 | width?: number | string; 15 | height?: number | string; 16 | style?: React.CSSProperties; 17 | duration?: string; 18 | } 19 | 20 | const FadingDots: React.FC> = ({ 21 | className = "", 22 | color = "#0d6efd", 23 | width = "2rem", 24 | height = "2rem", 25 | style = {}, 26 | duration = "0.6s", 27 | ...others 28 | }) => { 29 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 31 | 32 | return ( 33 |
div { 47 | width: calc(${resolvedWidth} / 6); 48 | height: calc(${resolvedHeight} / 6); 49 | background-color: ${color}; 50 | border-radius: 50%; 51 | animation: ${fade} ${duration} alternate ease-in-out infinite; 52 | } 53 | 54 | & > div:nth-of-type(2), 55 | & > div:nth-of-type(4) { 56 | animation-delay: calc(${duration} / 6); 57 | } 58 | 59 | & > div:nth-of-type(3), 60 | & > div:nth-of-type(5), 61 | & > div:nth-of-type(7) { 62 | animation-delay: calc(${duration} / 3); 63 | } 64 | 65 | & > div:nth-of-type(6), 66 | & > div:nth-of-type(8) { 67 | animation-delay: calc(${duration} / 2); 68 | } 69 | 70 | & > div:nth-of-type(9) { 71 | animation-delay: calc(${duration} * 2 / 3); 72 | } 73 | ` + ` ${className}` 74 | } 75 | > 76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | ); 87 | }; 88 | 89 | export default FadingDots; 90 | -------------------------------------------------------------------------------- /src/FillingBottle.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const spin = keyframes` 6 | 50%, 7 | 100% { 8 | transform: rotate(360deg); 9 | } 10 | `; 11 | 12 | const fill = keyframes` 13 | 25%, 14 | 50% { 15 | transform: scaleY(0); 16 | } 17 | 100% { 18 | transform: scaleY(1); 19 | } 20 | `; 21 | 22 | export interface FillingBottleProps { 23 | className?: string; 24 | color?: string; 25 | width?: number | string; 26 | height?: number | string; 27 | style?: React.CSSProperties; 28 | duration?: string; 29 | } 30 | 31 | const FillingBottle: React.FC> = ({ 32 | className = "", 33 | color = "#0d6efd", 34 | width = "2rem", 35 | height = "2rem", 36 | style = {}, 37 | duration = "1.4s", 38 | ...others 39 | }) => { 40 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 41 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 42 | 43 | return ( 44 |
72 | ); 73 | }; 74 | 75 | export default FillingBottle; 76 | -------------------------------------------------------------------------------- /src/FlippingSquare.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const flip = keyframes` 6 | 50% { 7 | transform: rotateY(180deg); 8 | } 9 | 100% { 10 | transform: rotateY(180deg) rotateX(180deg); 11 | } 12 | `; 13 | 14 | export interface FlippingSquareProps { 15 | className?: string; 16 | color?: string; 17 | width?: number | string; 18 | height?: number | string; 19 | style?: React.CSSProperties; 20 | duration?: string; 21 | } 22 | 23 | const FlippingSquare: React.FC> = ({ 24 | className = "", 25 | color = "#0d6efd", 26 | width = "2rem", 27 | height = "2rem", 28 | style = {}, 29 | duration = "1s", 30 | ...others 31 | }) => { 32 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 33 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 34 | 35 | return ( 36 |
45 |
54 |
55 | ); 56 | }; 57 | 58 | export default FlippingSquare; 59 | -------------------------------------------------------------------------------- /src/Hypnosis.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const spin = keyframes` 6 | to { 7 | transform: translate(-50%, -50%) rotate(360deg); 8 | } 9 | `; 10 | 11 | export interface HypnosisProps { 12 | className?: string; 13 | color?: string; 14 | width?: number | string; 15 | height?: number | string; 16 | style?: React.CSSProperties; 17 | duration?: string; 18 | } 19 | 20 | const Hypnosis: React.FC> = ({ 21 | className = "", 22 | color = "#0d6efd", 23 | width = "2rem", 24 | height = "2rem", 25 | style = {}, 26 | duration = "1.2s", 27 | ...others 28 | }) => { 29 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 31 | 32 | return ( 33 |
55 |
62 |
69 |
76 |
77 | ); 78 | }; 79 | 80 | export default Hypnosis; 81 | -------------------------------------------------------------------------------- /src/Messaging.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const wave = keyframes` 6 | from { 7 | transform: translateY(-60%); 8 | } 9 | to { 10 | transform: translateY(60%); 11 | } 12 | `; 13 | 14 | export interface MessagingProps { 15 | className?: string; 16 | color?: string; 17 | width?: number | string; 18 | height?: number | string; 19 | style?: React.CSSProperties; 20 | duration?: string; 21 | } 22 | 23 | const Messaging: React.FC> = ({ 24 | className = "", 25 | color = "#0d6efd", 26 | width = "1rem", 27 | height = "1rem", 28 | style = {}, 29 | duration = "0.4s", 30 | ...others 31 | }) => { 32 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 33 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 34 | 35 | return ( 36 |
65 |
66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | export default Messaging; 73 | -------------------------------------------------------------------------------- /src/Ring.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const spin = keyframes` 6 | to { 7 | transform: rotate(360deg); 8 | } 9 | `; 10 | 11 | export interface RingProps { 12 | className?: string; 13 | color?: string; 14 | width?: number | string; 15 | height?: number | string; 16 | style?: React.CSSProperties; 17 | duration?: string; 18 | } 19 | 20 | const Ring: React.FC> = ({ 21 | className = "", 22 | color = "#0d6efd", 23 | width = "3rem", 24 | height = "3rem", 25 | style = {}, 26 | duration = "1s", 27 | ...others 28 | }) => { 29 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 31 | 32 | return ( 33 | 46 | 57 | 66 | 67 | ); 68 | }; 69 | 70 | export default Ring; 71 | -------------------------------------------------------------------------------- /src/Spin.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const spin = keyframes` 6 | to { 7 | transform: rotate(360deg); 8 | } 9 | `; 10 | 11 | export interface SpinProps { 12 | className?: string; 13 | color?: string; 14 | width?: number | string; 15 | height?: number | string; 16 | style?: React.CSSProperties; 17 | duration?: string; 18 | } 19 | 20 | const Spin: React.FC> = ({ 21 | className = "", 22 | color = "#0d6efd", 23 | width = "2rem", 24 | height = "2rem", 25 | style = {}, 26 | duration = "1.2s", 27 | ...others 28 | }) => { 29 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 31 | 32 | return ( 33 |
47 | ); 48 | }; 49 | 50 | export default Spin; 51 | -------------------------------------------------------------------------------- /src/SpinStretch.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const spinStretch = keyframes` 6 | 50% { 7 | transform: rotate(360deg) scale(0.4, 0.33); 8 | border-width: 8px; 9 | } 10 | 100% { 11 | transform: rotate(720deg) scale(1, 1); 12 | border-width: 3px; 13 | } 14 | `; 15 | 16 | export interface SpinStretchProps { 17 | className?: string; 18 | color?: string; 19 | width?: number | string; 20 | height?: number | string; 21 | style?: React.CSSProperties; 22 | duration?: string; 23 | } 24 | 25 | const SpinStretch: React.FC> = ({ 26 | className = "", 27 | color = "#0d6efd", 28 | width = "2rem", 29 | height = "2rem", 30 | style = {}, 31 | duration = "1.2s", 32 | ...others 33 | }) => { 34 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 35 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 36 | 37 | return ( 38 |
53 | ); 54 | }; 55 | 56 | export default SpinStretch; 57 | -------------------------------------------------------------------------------- /src/TwinSpin.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | 3 | import React from "react"; 4 | 5 | const spin = keyframes` 6 | to { 7 | transform: rotate(360deg); 8 | } 9 | `; 10 | 11 | const pulse = keyframes` 12 | from { 13 | transform: scale(0.5); 14 | } 15 | to { 16 | transform: scale(1); 17 | } 18 | `; 19 | 20 | export interface TwinSpinProps { 21 | className?: string; 22 | color?: string; 23 | width?: number | string; 24 | height?: number | string; 25 | style?: React.CSSProperties; 26 | duration?: string; 27 | } 28 | 29 | const TwinSpin: React.FC> = ({ 30 | className = "", 31 | color = "#0d6efd", 32 | width = "2rem", 33 | height = "2rem", 34 | style = {}, 35 | duration = "0.6s", 36 | ...others 37 | }) => { 38 | let resolvedWidth = typeof width === "number" ? `${width}px` : width; 39 | let resolvedHeight = typeof height === "number" ? `${height}px` : height; 40 | 41 | return ( 42 |
69 | ); 70 | }; 71 | 72 | export default TwinSpin; 73 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as BarWave } from "./BarWave"; 2 | export { BarWaveProps } from "./BarWave"; 3 | export { default as BouncingBalls } from "./BouncingBalls"; 4 | export { BouncingBallsProps } from "./BouncingBalls"; 5 | export { default as CircularProgress } from "./CircularProgress"; 6 | export { CircularProgressProps } from "./CircularProgress"; 7 | export { default as Coin } from "./Coin"; 8 | export { CoinProps } from "./Coin"; 9 | export { default as FadingBalls } from "./FadingBalls"; 10 | export { FadingBallsProps } from "./FadingBalls"; 11 | export { default as FadingDots } from "./FadingDots"; 12 | export { FadingDotsProps } from "./FadingDots"; 13 | export { default as FillingBottle } from "./FillingBottle"; 14 | export { FillingBottleProps } from "./FillingBottle"; 15 | export { default as FlippingSquare } from "./FlippingSquare"; 16 | export { FlippingSquareProps } from "./FlippingSquare"; 17 | export { default as Hypnosis } from "./Hypnosis"; 18 | export { HypnosisProps } from "./Hypnosis"; 19 | export { default as Messaging } from "./Messaging"; 20 | export { MessagingProps } from "./Messaging"; 21 | export { default as Ring } from "./Ring"; 22 | export { RingProps } from "./Ring"; 23 | export { default as Spin } from "./Spin"; 24 | export { SpinProps } from "./Spin"; 25 | export { default as SpinStretch } from "./SpinStretch"; 26 | export { SpinStretchProps } from "./SpinStretch"; 27 | export { default as TwinSpin } from "./TwinSpin"; 28 | export { TwinSpinProps } from "./TwinSpin"; 29 | -------------------------------------------------------------------------------- /stories/BarWave.stories.tsx: -------------------------------------------------------------------------------- 1 | import BarWave, { BarWaveProps } from "../src/BarWave"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Bar Wave", 8 | component: BarWave, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/BouncingBalls.stories.tsx: -------------------------------------------------------------------------------- 1 | import BouncingBalls, { BouncingBallsProps } from "../src/BouncingBalls"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Bouncing Balls", 8 | component: BouncingBalls, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/CircularProgress.stories.tsx: -------------------------------------------------------------------------------- 1 | import CircularProgress, { CircularProgressProps } from "../src/CircularProgress"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Circular Progress", 8 | component: CircularProgress, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/Coin.stories.tsx: -------------------------------------------------------------------------------- 1 | import Coin, { CoinProps } from "../src/Coin"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Coin", 8 | component: Coin, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/FadingBalls.stories.tsx: -------------------------------------------------------------------------------- 1 | import FadingBalls, { FadingBallsProps } from "../src/FadingBalls"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Fading Balls", 8 | component: FadingBalls, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/FadingDots.stories.tsx: -------------------------------------------------------------------------------- 1 | import FadingDots, { FadingDotsProps } from "../src/FadingDots"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Fading Dots", 8 | component: FadingDots, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/FillingBottle.stories.tsx: -------------------------------------------------------------------------------- 1 | import FillingBottle, { FillingBottleProps } from "../src/FillingBottle"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Filling Bottle", 8 | component: FillingBottle, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/FlippingSquare.stories.tsx: -------------------------------------------------------------------------------- 1 | import FlippingSquare, { FlippingSquareProps } from "../src/FlippingSquare"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Flipping Square", 8 | component: FlippingSquare, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/Hypnosis.stories.tsx: -------------------------------------------------------------------------------- 1 | import Hypnosis, { HypnosisProps } from "../src/Hypnosis"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Hypnosis", 8 | component: Hypnosis, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/Messaging.stories.tsx: -------------------------------------------------------------------------------- 1 | import Messaging, { MessagingProps } from "../src/Messaging"; 2 | import { Meta, Story } from "@storybook/react"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Messaging", 8 | component: Messaging, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/Ring.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react"; 2 | import Ring, { RingProps } from "../src/Ring"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Ring", 8 | component: Ring, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/Spin.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react"; 2 | import Spin, { SpinProps } from "../src/Spin"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Spin", 8 | component: Spin, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/SpinStretch.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react"; 2 | import SpinStretch, { SpinStretchProps } from "../src/SpinStretch"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Spin Stretch", 8 | component: SpinStretch, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /stories/TwinSpin.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from "@storybook/react"; 2 | import TwinSpin, { TwinSpinProps } from "../src/TwinSpin"; 3 | 4 | import React from "react"; 5 | 6 | const meta: Meta = { 7 | title: "Twin Spin", 8 | component: TwinSpin, 9 | parameters: { 10 | controls: { expanded: true }, 11 | }, 12 | }; 13 | 14 | export default meta; 15 | 16 | const Template: Story = args => ; 17 | 18 | export const Default = Template.bind({}); 19 | 20 | Default.args = {}; 21 | -------------------------------------------------------------------------------- /test/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BarWave, 3 | BouncingBalls, 4 | CircularProgress, 5 | Coin, 6 | FadingBalls, 7 | FadingDots, 8 | FillingBottle, 9 | FlippingSquare, 10 | Hypnosis, 11 | Messaging, 12 | Ring, 13 | Spin, 14 | SpinStretch, 15 | TwinSpin, 16 | } from "../src"; 17 | 18 | import React from "react"; 19 | import { render } from "@testing-library/react"; 20 | 21 | describe("Render without crashing", () => { 22 | [ 23 | BarWave, 24 | BouncingBalls, 25 | CircularProgress, 26 | Coin, 27 | FadingBalls, 28 | FadingDots, 29 | FillingBottle, 30 | FlippingSquare, 31 | Hypnosis, 32 | Messaging, 33 | Ring, 34 | Spin, 35 | SpinStretch, 36 | TwinSpin, 37 | ].forEach(Component => { 38 | it(`Render ${Component.name}`, () => { 39 | render(); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | } 35 | } 36 | --------------------------------------------------------------------------------