Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ updates:
- dependency-name: "*"
update-types:
- "version-update:semver-major"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
107 changes: 88 additions & 19 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,79 @@ on:
pull_request:
branches:
- main
# Weekly check of the version published on npm against the latest React releases
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:

jobs:
integration:
name: Analyze Integration
package:
name: Test Package (React ${{ matrix.react }})
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
react: [18, 19]

steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install Dependencies
run: npm ci

- name: Execute Rollup Script
run: npm run rollup
- name: Install React ${{ matrix.react }}
run: npm i --no-save react@${{ matrix.react }} react-dom@${{ matrix.react }} @types/react@${{ matrix.react }}

- name: Build Package
run: npm run build

- name: Run Unit Tests
run: npm test

- name: Check Types
run: npm run test:types

- name: Check Package Exports
run: npm run test:package

integration:
name: Analyze Integration (React ${{ matrix.react }})
needs: package
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
react: [18, 19]

steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Pack Local Package
if: github.event_name != 'schedule'
run: |
npm ci
npm run build
npm pack
echo "PACKAGE=$PWD/$(ls fetch-loading-*.tgz)" >> "$GITHUB_ENV"

- name: Publish Repository to Yalc
run: npx yalc publish
- name: Use Published Package
if: github.event_name == 'schedule'
run: echo "PACKAGE=fetch-loading@latest" >> "$GITHUB_ENV"

- name: Create Integration Project
run: npm create vite@latest integration -- --template react-ts
Expand All @@ -38,28 +88,27 @@ jobs:
run: |
cd integration
npm i
npx yalc add fetch-loading
npm i
npm i react@${{ matrix.react }} react-dom@${{ matrix.react }} @types/react@${{ matrix.react }} @types/react-dom@${{ matrix.react }}
npm i "$PACKAGE" puppeteer
cat package.json | grep fetch-loading || exit 1

- name: Override App.jsx
- name: Override App.tsx
run: |
cat <<EOF > integration/src/App.tsx

import { FetchLoading } from 'fetch-loading'

const App = () => {
return <FetchLoading ariaLabel="Loading" theme="#ffe98c" />
return (
<div style={{ height: 200 }}>
<FetchLoading ariaLabel="Loading" theme="#ffe98c" size={20} center />
</div>
)
}

export default App
EOF

- name: Install Puppeteer
run: |
cd integration
npm i puppeteer

- name: Build Integration Project
run: |
cd integration
Expand All @@ -75,8 +124,28 @@ jobs:
})
const page = await browser.newPage()
try {
await page.goto('http://localhost:5173', { waitUntil: 'networkidle0' })
await page.goto('http://localhost:4173', { waitUntil: 'networkidle0' })
await page.waitForSelector('div[aria-label="Loading"]', { timeout: 5000 })
const result = await page.evaluate(() => {
const dots = [...document.querySelectorAll('.fetch-loading__dot')]
const style = getComputedStyle(dots[0])
return {
dots: dots.length,
width: style.width,
color: style.backgroundColor,
animation: style.animationName,
}
})
console.log(result)
const expected = {
dots: 3,
width: '20px',
color: 'rgb(255, 233, 140)',
animation: 'fetch-loading',
}
for (const [key, value] of Object.entries(expected)) {
if (result[key] !== value) throw new Error(key + ' is ' + result[key] + ', expected ' + value)
}
} catch (e) {
console.error('Error:', e)
process.exit(1)
Expand All @@ -89,6 +158,6 @@ jobs:
- name: Test for FetchLoading Component
run: |
cd integration
npm run dev -- --port 5173 --host &
npx wait-on http://localhost:5173 --timeout 10000
npm run preview -- --port 4173 --host &
npx wait-on http://localhost:4173 --timeout 10000
node integration.mjs
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/dist
/node_modules
/node_modules
/*.tgz
75 changes: 74 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { FetchLoading } from 'fetch-loading'

![Default Fetch Loading Animation Preview](https://github.com/user-attachments/assets/b4ee8647-0b14-4af4-8f67-cba615b31031)

You can also pass any Hex- / RGB- or RGBA-value as an argument for the `theme` property:
You can pass any valid CSS color (Hex, RGB(A), HSL, named colors, `var(--my-color)`, …) as an argument for the `theme` property:

```
...
Expand All @@ -48,6 +48,10 @@ You can also pass any Hex- / RGB- or RGBA-value as an argument for the `theme` p

<FetchLoading theme="rgba(131, 240, 131, 0.8)" />

<FetchLoading theme="hsl(200 80% 50%)" />

<FetchLoading theme="var(--color-primary)" />

...
```

Expand Down Expand Up @@ -78,3 +82,72 @@ Of course, `theme` and `ariaLabel` can also be used simultaneously:

...
```

## Size

The `size` property sets the size of a single dot. The gap, padding and bounce height scale with it. Numbers are treated as pixels, strings can be any CSS length. The default is `12px`.

```
<FetchLoading size={8} />

<FetchLoading />

<FetchLoading size="1.5rem" />
```

![Size Fetch Loading Animation Preview](https://raw.githubusercontent.com/StackOverflowIsBetterThanAnyAI/fetch-loading/main/assets/size.svg?sanitize=true)

## Centering

With `center`, the animation fills its parent and is centered horizontally and vertically. For vertical centering, the parent needs a defined height.

```
<div style={{ height: 200 }}>
<FetchLoading center />
</div>
```

![Centered Fetch Loading Animation Preview](https://raw.githubusercontent.com/StackOverflowIsBetterThanAnyAI/fetch-loading/main/assets/center.svg?sanitize=true)

## Styling with CSS

`className`, `style` and all other `div` attributes (e.g. `data-testid`) are passed to the wrapper element. The built-in styles have zero specificity, so your own CSS always wins.

Color and size can also be set via CSS custom properties, e.g. for theming:

```
.my-loader {
--fetch-loading-color: tomato;
--fetch-loading-size: 16px;
}
```

```
<FetchLoading className="my-loader" />
```

## Reduced Motion

Users with `prefers-reduced-motion` enabled see a gentle fade instead of the bouncing animation. No configuration is needed, the operating system setting is respected automatically:

![Reduced Motion Fetch Loading Animation Preview](https://raw.githubusercontent.com/StackOverflowIsBetterThanAnyAI/fetch-loading/main/assets/reduced-motion.svg?sanitize=true)

## Props

| Prop | Type | Default | Description |
| ----------- | ------------------ | --------- | --------------------------------------------- |
| `ariaLabel` | `string` | – | Adds `role="status"`, `aria-live` and label |
| `center` | `boolean` | `false` | Centers the animation inside its parent |
| `size` | `number \| string` | `12px` | Size of a single dot |
| `theme` | `string` | `#52525c` | Any valid CSS color |

## Development

```
npm run build # bundle ESM, CJS and type declarations into dist/
npm test # render tests for the ESM and CJS build
npm run test:types # type check a consumer file against the declarations
npm run test:package # lint package.json exports and types (publint, attw)
```

All checks also run automatically before `npm publish`. The CI runs them against React 18 and 19 and additionally builds a Vite app with the packed package and checks it in a headless browser. Once a week, the same check runs against the version published on npm.
69 changes: 69 additions & 0 deletions assets/center.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading