├── .nvmrc ├── src ├── test │ └── setup.ts ├── index.test.tsx ├── Spin.tsx ├── Coin.tsx ├── FlippingSquare.tsx ├── SpinStretch.tsx ├── index.tsx ├── Ring.tsx ├── FadingBalls.tsx ├── Messaging.tsx ├── TwinSpin.tsx ├── FillingBottle.tsx ├── CircularProgress.tsx ├── BouncingBalls.tsx ├── BarWave.tsx ├── Hypnosis.tsx └── FadingDots.tsx ├── .npmrc ├── .storybook ├── preview.ts └── main.ts ├── vitest.config.ts ├── tsup.config.ts ├── stories ├── Coin.stories.tsx ├── Ring.stories.tsx ├── Spin.stories.tsx ├── BarWave.stories.tsx ├── Hypnosis.stories.tsx ├── TwinSpin.stories.tsx ├── Messaging.stories.tsx ├── FadingDots.stories.tsx ├── FadingBalls.stories.tsx ├── SpinStretch.stories.tsx ├── BouncingBalls.stories.tsx ├── FillingBottle.stories.tsx ├── FlippingSquare.stories.tsx └── CircularProgress.stories.tsx ├── .github └── workflows │ ├── size.yml │ └── main.yml ├── .gitignore ├── tsconfig.json ├── .eslintrc.cjs ├── LICENSE ├── CHANGELOG.md ├── package.json ├── DEVELOPMENT.md └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /src/test/setup.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | save-exact=true 3 | package-lock=true -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | const preview = { 2 | parameters: { 3 | actions: { argTypesRegex: "^on[A-Z].*" }, 4 | controls: { 5 | matchers: { 6 | color: /(background|color)$/i, 7 | date: /Date$/, 8 | }, 9 | }, 10 | }, 11 | }; 12 | 13 | export default preview; 14 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | globals: true, 8 | environment: "jsdom", 9 | setupFiles: ["./src/test/setup.ts"], 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.tsx"], 5 | format: ["esm"], 6 | dts: true, 7 | clean: true, 8 | sourcemap: true, 9 | minify: true, 10 | external: ["react", "react-dom"], 11 | treeshake: true, 12 | splitting: false, 13 | bundle: true, 14 | }); 15 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout repo 8 | uses: actions/checkout@v4 9 | 10 | - name: Use Node 18 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: "18" 14 | cache: "npm" 15 | 16 | - name: Install dependencies 17 | run: npm ci 18 | 19 | - name: Check bundle size 20 | uses: andresz1/size-limit-action@v1 21 | with: 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/react-vite'; 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], 5 | addons: [ 6 | '@storybook/addon-links', 7 | '@storybook/addon-essentials', 8 | '@storybook/addon-interactions', 9 | ], 10 | framework: { 11 | name: '@storybook/react-vite', 12 | options: {}, 13 | }, 14 | typescript: { 15 | check: false, 16 | reactDocgen: 'react-docgen-typescript', 17 | reactDocgenTypescriptOptions: { 18 | shouldExtractLiteralValuesFromEnum: true, 19 | propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true), 20 | }, 21 | }, 22 | }; 23 | 24 | export default config; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build outputs 8 | dist/ 9 | build/ 10 | *.tsbuildinfo 11 | 12 | # Runtime data 13 | *.log 14 | *.pid 15 | *.seed 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage/ 19 | 20 | # Storybook build outputs 21 | storybook-static/ 22 | 23 | # Environment variables 24 | .env 25 | .env.local 26 | .env.development.local 27 | .env.test.local 28 | .env.production.local 29 | 30 | # IDEs and editors 31 | .vscode/ 32 | .idea/ 33 | *.swp 34 | *.swo 35 | *~ 36 | 37 | # OS generated files 38 | .DS_Store 39 | .DS_Store? 40 | ._* 41 | .Spotlight-V100 42 | .Trashes 43 | ehthumbs.db 44 | Thumbs.db 45 | 46 | # Temporary folders 47 | tmp/ 48 | temp/ 49 | 50 | # Cache directories 51 | .cache/ 52 | .parcel-cache/ 53 | .eslintcache 54 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 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: ["18.x", "20.x"] 11 | os: [ubuntu-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node }} 21 | cache: "npm" 22 | 23 | - name: Install dependencies 24 | run: npm ci 25 | 26 | - name: Type check 27 | run: npm run type-check 28 | 29 | - name: Lint 30 | run: npm run lint 31 | 32 | - name: Test 33 | run: npm test 34 | 35 | - name: Build 36 | run: npm run build 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": ["dom", "dom.iterable", "ES6"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "ESNext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | "declaration": true, 18 | "outDir": "./dist", 19 | "rootDir": "./src", 20 | "sourceMap": true, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedIndexedAccess": true, 24 | "noImplicitOverride": true, 25 | "exactOptionalPropertyTypes": true 26 | }, 27 | "include": ["src/**/*"], 28 | "exclude": ["node_modules", "dist", "**/*.stories.*", "**/*.test.*"] 29 | } 30 | -------------------------------------------------------------------------------- /src/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/react"; 2 | import { describe, it, expect } from "vitest"; 3 | import { 4 | BarWave, 5 | BouncingBalls, 6 | CircularProgress, 7 | Coin, 8 | FadingBalls, 9 | FadingDots, 10 | FillingBottle, 11 | FlippingSquare, 12 | Hypnosis, 13 | Messaging, 14 | Ring, 15 | Spin, 16 | SpinStretch, 17 | TwinSpin, 18 | } from "./index"; 19 | 20 | describe("Render without crashing", () => { 21 | const components = [ 22 | BarWave, 23 | BouncingBalls, 24 | CircularProgress, 25 | Coin, 26 | FadingBalls, 27 | FadingDots, 28 | FillingBottle, 29 | FlippingSquare, 30 | Hypnosis, 31 | Messaging, 32 | Ring, 33 | Spin, 34 | SpinStretch, 35 | TwinSpin, 36 | ]; 37 | 38 | components.forEach((Component) => { 39 | it(`Render ${Component.name}`, () => { 40 | const { container } = render(); 41 | expect(container).toBeDefined(); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true, node: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | "plugin:react/recommended", 9 | "plugin:prettier/recommended", 10 | ], 11 | ignorePatterns: ["dist", ".eslintrc.cjs", "*.config.*"], 12 | parser: "@typescript-eslint/parser", 13 | parserOptions: { 14 | ecmaVersion: "latest", 15 | sourceType: "module", 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | }, 20 | plugins: ["react-refresh", "@typescript-eslint", "react"], 21 | rules: { 22 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], 23 | "react/react-in-jsx-scope": "off", 24 | "react/prop-types": "off", 25 | "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], 26 | }, 27 | settings: { 28 | react: { 29 | version: "detect", 30 | }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 31 | 32 | return ( 33 |
47 | ); 48 | }; 49 | 50 | export default Spin; 51 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 31 | 32 | return ( 33 |
42 |
51 |
52 | ); 53 | }; 54 | 55 | export default Coin; 56 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 33 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 34 | 35 | return ( 36 |
45 |
54 |
55 | ); 56 | }; 57 | 58 | export default FlippingSquare; 59 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 35 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 36 | 37 | return ( 38 |
53 | ); 54 | }; 55 | 56 | export default SpinStretch; 57 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as BarWave } from "./BarWave"; 2 | export type { BarWaveProps } from "./BarWave"; 3 | export { default as BouncingBalls } from "./BouncingBalls"; 4 | export type { BouncingBallsProps } from "./BouncingBalls"; 5 | export { default as CircularProgress } from "./CircularProgress"; 6 | export type { CircularProgressProps } from "./CircularProgress"; 7 | export { default as Coin } from "./Coin"; 8 | export type { CoinProps } from "./Coin"; 9 | export { default as FadingBalls } from "./FadingBalls"; 10 | export type { FadingBallsProps } from "./FadingBalls"; 11 | export { default as FadingDots } from "./FadingDots"; 12 | export type { FadingDotsProps } from "./FadingDots"; 13 | export { default as FillingBottle } from "./FillingBottle"; 14 | export type { FillingBottleProps } from "./FillingBottle"; 15 | export { default as FlippingSquare } from "./FlippingSquare"; 16 | export type { FlippingSquareProps } from "./FlippingSquare"; 17 | export { default as Hypnosis } from "./Hypnosis"; 18 | export type { HypnosisProps } from "./Hypnosis"; 19 | export { default as Messaging } from "./Messaging"; 20 | export type { MessagingProps } from "./Messaging"; 21 | export { default as Ring } from "./Ring"; 22 | export type { RingProps } from "./Ring"; 23 | export { default as Spin } from "./Spin"; 24 | export type { SpinProps } from "./Spin"; 25 | export { default as SpinStretch } from "./SpinStretch"; 26 | export type { SpinStretchProps } from "./SpinStretch"; 27 | export { default as TwinSpin } from "./TwinSpin"; 28 | export type { TwinSpinProps } from "./TwinSpin"; 29 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 31 | 32 | return ( 33 | 45 | 56 | 65 | 66 | ); 67 | }; 68 | 69 | export default Ring; 70 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [3.0.0] - 2025-09-26 9 | 10 | ### Changed 11 | - **BREAKING**: Migrated from TSDX to tsup for faster builds 12 | - **BREAKING**: Now ESM-only package (no CommonJS support) 13 | - **BREAKING**: Minimum Node.js version is now 18+ 14 | - **BREAKING**: Updated React peer dependency to 18+ 15 | - Updated TypeScript to v5.6.2 16 | - Updated Storybook to v8.3.5 with Vite integration 17 | - Migrated from Jest to Vitest for testing 18 | - Updated ESLint configuration to modern standards 19 | - Updated all dependencies to latest stable versions 20 | 21 | ### Added 22 | - Modern tsup build configuration with tree-shaking support 23 | - Vitest for faster test execution with native ESM support 24 | - Development guide (`DEVELOPMENT.md`) 25 | - Modern `.gitignore` with comprehensive patterns 26 | - `.nvmrc` for Node.js version management 27 | - `.npmrc` for npm configuration 28 | - Enhanced TypeScript configuration with stricter type checking 29 | - Support for `isolatedModules` compilation 30 | 31 | ### Fixed 32 | - Fixed duration prop usage in Spin component 33 | - Removed invalid `crossOrigin` attributes from SVG elements 34 | - Fixed TypeScript type re-exports with proper `export type` syntax 35 | 36 | ### Removed 37 | - TSDX dependency and configuration 38 | - CommonJS build output 39 | - Legacy Babel configuration 40 | - Husky pre-commit hooks (can be re-added if needed) 41 | - Jest configuration 42 | 43 | ## [2.1.0] - Previous versions 44 | 45 | Previous versions used TSDX for building and Jest for testing. See git history for details. -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 33 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 34 | 35 | return ( 36 |
65 |
66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | export default FadingBalls; 73 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 33 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 34 | 35 | return ( 36 |
65 |
66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | export default Messaging; 73 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 39 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 40 | 41 | return ( 42 |
69 | ); 70 | }; 71 | 72 | export default TwinSpin; 73 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 41 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 42 | 43 | return ( 44 |
72 | ); 73 | }; 74 | 75 | export default FillingBottle; 76 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 44 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 45 | 46 | return ( 47 | 60 | 74 | 75 | ); 76 | }; 77 | 78 | export default CircularProgress; 79 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 40 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 41 | 42 | return ( 43 |
73 |
74 |
75 |
76 |
77 | ); 78 | }; 79 | 80 | export default BouncingBalls; 81 | -------------------------------------------------------------------------------- /src/BarWave.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "goober"; 2 | import React from "react"; 3 | 4 | const grow = keyframes` 5 | 0%, 100% { 6 | transform: scaleY(1); 7 | } 8 | 9 | 50% { 10 | transform: scaleY(2); 11 | } 12 | `; 13 | 14 | export interface BarWaveProps { 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 BarWave: React.FC> = ({ 24 | className = "", 25 | color = "#0d6efd", 26 | width = "2rem", 27 | height = "1rem", 28 | style = {}, 29 | duration = "1s", 30 | ...others 31 | }) => { 32 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 33 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 34 | 35 | return ( 36 |
71 | 72 | 73 | 74 | 75 |
76 | ); 77 | }; 78 | 79 | export default BarWave; 80 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 31 | 32 | return ( 33 |
55 |
62 |
69 |
76 |
77 | ); 78 | }; 79 | 80 | export default Hypnosis; 81 | -------------------------------------------------------------------------------- /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 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 30 | const 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-cssfx-loading", 3 | "version": "3.0.1", 4 | "description": "React wrapper for the CSSFX loading collection", 5 | "author": "napthedev", 6 | "license": "MIT", 7 | "type": "module", 8 | "main": "./dist/index.js", 9 | "module": "./dist/index.js", 10 | "types": "./dist/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "import": "./dist/index.js", 14 | "types": "./dist/index.d.ts" 15 | } 16 | }, 17 | "files": [ 18 | "dist", 19 | "src" 20 | ], 21 | "engines": { 22 | "node": ">=18" 23 | }, 24 | "scripts": { 25 | "dev": "tsup --watch", 26 | "build": "tsup", 27 | "test": "vitest run", 28 | "test:watch": "vitest", 29 | "lint": "eslint src --ext .ts,.tsx --fix", 30 | "type-check": "tsc --noEmit", 31 | "prepare": "npm run build", 32 | "size": "size-limit", 33 | "analyze": "size-limit --why", 34 | "storybook": "storybook dev -p 6006", 35 | "build-storybook": "storybook build" 36 | }, 37 | "peerDependencies": { 38 | "react": ">=18", 39 | "react-dom": ">=18" 40 | }, 41 | "dependencies": { 42 | "goober": "^2.1.14" 43 | }, 44 | "devDependencies": { 45 | "@size-limit/preset-small-lib": "^11.1.6", 46 | "@storybook/addon-essentials": "^8.3.5", 47 | "@storybook/addon-interactions": "^8.3.5", 48 | "@storybook/addon-links": "^8.3.5", 49 | "@storybook/blocks": "^8.3.5", 50 | "@storybook/react": "^8.3.5", 51 | "@storybook/react-vite": "^8.3.5", 52 | "@testing-library/jest-dom": "^6.5.0", 53 | "@testing-library/react": "^16.0.1", 54 | "@types/react": "^18.3.11", 55 | "@types/react-dom": "^18.3.0", 56 | "@typescript-eslint/eslint-plugin": "^8.8.0", 57 | "@typescript-eslint/parser": "^8.8.0", 58 | "@vitejs/plugin-react": "^4.3.2", 59 | "eslint": "^8.57.1", 60 | "eslint-config-prettier": "^9.1.0", 61 | "eslint-plugin-prettier": "^5.2.1", 62 | "eslint-plugin-react": "^7.37.0", 63 | "eslint-plugin-react-hooks": "^4.6.2", 64 | "eslint-plugin-react-refresh": "^0.4.12", 65 | "jsdom": "^25.0.1", 66 | "prettier": "^3.3.3", 67 | "react": "^18.3.1", 68 | "react-dom": "^18.3.1", 69 | "size-limit": "^11.1.6", 70 | "storybook": "^8.3.5", 71 | "tsup": "^8.3.0", 72 | "typescript": "^5.6.2", 73 | "vite": "^5.4.8", 74 | "vitest": "^2.1.2" 75 | }, 76 | "prettier": { 77 | "printWidth": 100, 78 | "semi": true, 79 | "singleQuote": false, 80 | "trailingComma": "es5" 81 | }, 82 | "size-limit": [ 83 | { 84 | "path": "dist/index.js", 85 | "limit": "20 KB" 86 | } 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development Guide 2 | 3 | This project uses modern tooling for React component library development. 4 | 5 | ## Prerequisites 6 | 7 | - Node.js >= 18 8 | - npm >= 9 9 | 10 | ## Project Structure 11 | 12 | ``` 13 | src/ # Source code 14 | ├── *.tsx # React components 15 | ├── index.tsx # Main exports 16 | ├── index.test.tsx # Tests 17 | └── test/setup.ts # Test setup 18 | 19 | stories/ # Storybook stories 20 | dist/ # Built output (generated) 21 | .storybook/ # Storybook configuration 22 | ``` 23 | 24 | ## Development Tools 25 | 26 | - **tsup** - Fast TypeScript bundler for building the library 27 | - **Vitest** - Modern test runner for unit tests 28 | - **Storybook 8** - Component development and documentation 29 | - **ESLint + Prettier** - Code linting and formatting 30 | - **TypeScript 5** - Type checking 31 | 32 | ## Available Scripts 33 | 34 | ### Development 35 | ```bash 36 | npm run dev # Watch mode build with tsup 37 | npm run storybook # Start Storybook development server 38 | ``` 39 | 40 | ### Building 41 | ```bash 42 | npm run build # Production build 43 | npm run build-storybook # Build Storybook for deployment 44 | ``` 45 | 46 | ### Testing & Quality 47 | ```bash 48 | npm test # Run tests with Vitest 49 | npm run test:watch # Run tests in watch mode 50 | npm run lint # Lint and fix code with ESLint 51 | npm run type-check # TypeScript type checking 52 | ``` 53 | 54 | ## Adding New Components 55 | 56 | 1. Create component file in `src/ComponentName.tsx` 57 | 2. Export from `src/index.tsx` 58 | 3. Create story in `stories/ComponentName.stories.tsx` 59 | 4. Add to test in `src/index.test.tsx` 60 | 61 | ### Component Template 62 | 63 | ```typescript 64 | import { css, keyframes } from "goober"; 65 | import React from "react"; 66 | 67 | const animation = keyframes` 68 | // keyframes here 69 | `; 70 | 71 | export interface ComponentNameProps { 72 | className?: string; 73 | color?: string; 74 | width?: number | string; 75 | height?: number | string; 76 | style?: React.CSSProperties; 77 | duration?: string; 78 | } 79 | 80 | const ComponentName: React.FC> = ({ 81 | className = "", 82 | color = "#0d6efd", 83 | width = "2rem", 84 | height = "2rem", 85 | style = {}, 86 | duration = "1s", 87 | ...others 88 | }) => { 89 | const resolvedWidth = typeof width === "number" ? `${width}px` : width; 90 | const resolvedHeight = typeof height === "number" ? `${height}px` : height; 91 | 92 | return ( 93 |
103 | {/* content */} 104 |
105 | ); 106 | }; 107 | 108 | export default ComponentName; 109 | ``` 110 | 111 | ## CI/CD 112 | 113 | The project uses GitHub Actions for continuous integration: 114 | 115 | - **`.github/workflows/main.yml`** - Runs on push and PR, tests on Node 18 & 20 116 | - **`.github/workflows/size.yml`** - Checks bundle size on pull requests 117 | 118 | The CI pipeline runs: 119 | 1. Type checking with TypeScript 120 | 2. Code linting with ESLint 121 | 3. Unit tests with Vitest 122 | 4. Production build with tsup 123 | 124 | ## Publishing 125 | 126 | The package uses modern ESM-only build with proper TypeScript declarations. 127 | 128 | ```bash 129 | npm run build 130 | npm publish 131 | ``` 132 | 133 | ## Migration Notes 134 | 135 | This project was migrated from TSDX to modern tooling: 136 | 137 | - **Build**: TSDX → tsup (faster, simpler) 138 | - **Tests**: Jest → Vitest (faster, native ESM) 139 | - **Storybook**: v6 → v8 (latest features) 140 | - **Output**: CJS/ESM → ESM only (modern standard) 141 | - **Dependencies**: Updated to latest stable versions 142 | 143 | The build output is now ESM-only, which is the modern standard for npm packages. The package supports tree-shaking and is optimized for modern bundlers. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎨 React CSSFx Loading 2 | 3 | **Beautiful, lightweight loading animations for React - because users deserve delightful waiting experiences! ✨** 4 | 5 | Transform your loading states from boring to brilliant with this carefully curated collection of CSS-powered animations. Built as a React wrapper for the stunning [CSSFx collection](https://cssfx.netlify.app/), this package brings you 14 gorgeous loading components that are both performant and pixel-perfect. 6 | 7 | ## ✨ Why Choose React CSSFx Loading? 8 | 9 | - 🚀 **Lightweight & Fast** - Pure CSS animations with zero JavaScript overhead 10 | - 🎯 **Tree-shakable** - Import only what you need to keep bundles small 11 | - 🎨 **Highly Customizable** - Colors, sizes, timing - make it yours! 12 | - 📱 **Responsive Ready** - Looks great on every device 13 | - ♿ **Accessible** - Respects user preferences for reduced motion 14 | - 🔧 **TypeScript Support** - Full type safety out of the box 15 | 16 | ## 🎬 See It In Action 17 | 18 | ![Preview](https://res.cloudinary.com/naptest/image/upload/v1634719726/cssfx_ecuj37.gif) 19 | 20 | **👀 Live Examples** 21 | - 🌐 [Interactive Demo](https://react-cssfx.surge.sh/) - Try all components live! 22 | - 📚 [Storybook Documentation](https://cssfx-storybook.vercel.app/) - Explore every prop and variation 23 | 24 | ## 🚀 Quick Start 25 | 26 | Get up and running in seconds! Install with your favorite package manager: 27 | 28 | ```bash 29 | # npm 30 | npm install react-cssfx-loading 31 | 32 | # yarn 33 | yarn add react-cssfx-loading 34 | 35 | # pnpm 36 | pnpm add react-cssfx-loading 37 | 38 | # bun 39 | bun add react-cssfx-loading 40 | ``` 41 | 42 | ## 💫 Usage 43 | 44 | It's as simple as import and drop! Here's how to get started: 45 | 46 | ```typescript 47 | import { BarWave } from "react-cssfx-loading"; 48 | 49 | // 🎯 Simple and clean 50 | 51 | 52 | // 🎨 Make it your own 53 | 59 | 60 | // 🔧 Full HTML support - it's just a div with superpowers! 61 | console.log("Loading animation clicked!")} 63 | className="my-spinner" 64 | style={{ margin: "auto" }} 65 | aria-label="Loading content..." 66 | /> 67 | ``` 68 | 69 | ## 🎛️ Props & Customization 70 | 71 | Every component is built with flexibility in mind. Here's what you can customize: 72 | 73 | | Prop | Type | Default | Description | 74 | |------|------|---------|-------------| 75 | | `color` | `string` | `#0d6efd` | 🎨 Animation color - any valid CSS color | 76 | | `width` | `string` | varies | 📏 Component width (e.g., "50px", "3rem") | 77 | | `height` | `string` | varies | 📐 Component height (e.g., "50px", "3rem") | 78 | | `duration` | `string` | `1s` | ⏱️ Animation speed (e.g., "2s", "500ms") | 79 | | `className` | `string` | - | 🏷️ Additional CSS classes | 80 | | `style` | `CSSProperties` | - | 💅 Inline styles object | 81 | | ...rest | `HTMLDivElement` | - | 🔧 All standard HTML div attributes | 82 | 83 | ## 🎨 Component Gallery 84 | 85 | Choose from 14 beautifully crafted loading animations: 86 | 87 | ### 🌊 Motion & Flow 88 | - `BarWave` - Elegant wave motion 89 | - `BouncingBalls` - Playful bouncing spheres 90 | - `FadingBalls` - Smooth fade transitions 91 | - `FadingDots` - Subtle dot sequence 92 | 93 | ### 🔄 Rotational 94 | - `CircularProgress` - Classic progress circle 95 | - `Coin` - Charming coin flip effect 96 | - `Hypnosis` - Mesmerizing spiral 97 | - `Ring` - Clean rotating ring 98 | - `Spin` - Simple rotation 99 | - `TwinSpin` - Dual rotation magic 100 | 101 | ### 📦 Geometric 102 | - `FlippingSquare` - Dynamic square flip 103 | - `SpinStretch` - Stretching rotation 104 | - `FillingBottle` - Liquid fill animation 105 | - `Messaging` - Chat bubble effect 106 | 107 | --- 108 | 109 | ## 🤝 Contributing 110 | 111 | We love contributions! Feel free to: 112 | - 🐛 Report bugs 113 | - 💡 Suggest new animations 114 | - 🔧 Submit pull requests 115 | - ⭐ Star the repo if you find it helpful! 116 | 117 | ## 📄 License 118 | 119 | MIT © [napthedev](https://github.com/napthedev) 120 | 121 | --- 122 | 123 |
124 | 125 | **Made with ❤️ for the React community** 126 | 127 | *Making loading states delightful, one animation at a time* ✨ 128 | 129 |
130 | --------------------------------------------------------------------------------