mirror of
https://github.com/rive-app/rive-react.git
synced 2026-03-13 08:22:30 +08:00
Compare commits
373 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d303e8c96f | ||
|
|
479d5340e8 | ||
|
|
ff3976d9fe | ||
|
|
f2a6860292 | ||
|
|
8213c6ed7e | ||
|
|
a9a98fece2 | ||
|
|
4052b7492b | ||
|
|
8bb6f1dc58 | ||
|
|
6d6b68e868 | ||
|
|
d3305f8afe | ||
|
|
9eb292c2c5 | ||
|
|
7277ed2f0d | ||
|
|
5354d1f69b | ||
|
|
86e67d5f11 | ||
|
|
e5c751aa54 | ||
|
|
60b1dda98d | ||
|
|
4914770c70 | ||
|
|
21e457bda4 | ||
|
|
cc0e91689b | ||
|
|
8244982000 | ||
|
|
439b413782 | ||
|
|
9ab4e378fc | ||
|
|
9061a93978 | ||
|
|
88d1bc000c | ||
|
|
9b1d3825f8 | ||
|
|
277f4d7db0 | ||
|
|
e68ca963a9 | ||
|
|
d820cdf6f1 | ||
|
|
c9a4dc0b5c | ||
|
|
19b58f5eb6 | ||
|
|
6263449898 | ||
|
|
630f70a81d | ||
|
|
bbd0d08fed | ||
|
|
ef23793d26 | ||
|
|
ca10584ed4 | ||
|
|
6fe765df84 | ||
|
|
c62b80af09 | ||
|
|
777337c481 | ||
|
|
da4b5978e7 | ||
|
|
09c0d1f947 | ||
|
|
0ca5933c41 | ||
|
|
f272fd92f0 | ||
|
|
192a562764 | ||
|
|
224a768737 | ||
|
|
c085bd5ea3 | ||
|
|
bb13c71982 | ||
|
|
779e06583c | ||
|
|
e172e0f210 | ||
|
|
abbcf14b70 | ||
|
|
a0885648ce | ||
|
|
9f768bdd0c | ||
|
|
94c9a2a60b | ||
|
|
dfe0d10936 | ||
|
|
d27eb7254d | ||
|
|
c0b27bd018 | ||
|
|
739ac7c370 | ||
|
|
b1b8040b4f | ||
|
|
1f6ae83de3 | ||
|
|
e5ff233c34 | ||
|
|
64795564d8 | ||
|
|
f0b32056a4 | ||
|
|
b5dc2fa8da | ||
|
|
0108d425dd | ||
|
|
74a27143b4 | ||
|
|
2cfb8a4955 | ||
|
|
af6c3f84c6 | ||
|
|
351d9966b1 | ||
|
|
ba73cb7053 | ||
|
|
4a4f8a7151 | ||
|
|
b5baef5ce7 | ||
|
|
79f80d39cd | ||
|
|
e1ed8250e3 | ||
|
|
a40ba0dacc | ||
|
|
7ea9c8fbcb | ||
|
|
d4cef9a131 | ||
|
|
99489af287 | ||
|
|
d881bb18c8 | ||
|
|
2e461c66b3 | ||
|
|
bb078296d0 | ||
|
|
f9bad8b62d | ||
|
|
e42f5e32d7 | ||
|
|
83fa6635ec | ||
|
|
bdd4c84d8b | ||
|
|
414543d37d | ||
|
|
119f7eea2a | ||
|
|
92b0342882 | ||
|
|
38b0a1cc86 | ||
|
|
2fd87e45e8 | ||
|
|
dc99fe27cc | ||
|
|
2358c82626 | ||
|
|
0475d47535 | ||
|
|
f51e538479 | ||
|
|
29fa6e76fb | ||
|
|
2459c489bf | ||
|
|
927dbc6b32 | ||
|
|
8ebbfb164f | ||
|
|
388fd16b5b | ||
|
|
8c87bc0ab7 | ||
|
|
280b2ec4a4 | ||
|
|
55a40c7d9f | ||
|
|
d4e6243dbb | ||
|
|
dc738d00a6 | ||
|
|
2ae22d6c30 | ||
|
|
4a3fc22261 | ||
|
|
bdc734b90b | ||
|
|
eeda717178 | ||
|
|
178f04df61 | ||
|
|
ef11079784 | ||
|
|
11f548b21c | ||
|
|
d0ff23c124 | ||
|
|
a83f5a275a | ||
|
|
b437230063 | ||
|
|
10912052f2 | ||
|
|
fe8265cd58 | ||
|
|
5b458b76e5 | ||
|
|
1decbc7d85 | ||
|
|
87627125f5 | ||
|
|
e823e9519f | ||
|
|
ac88187118 | ||
|
|
1897e823b9 | ||
|
|
1a2e57a82c | ||
|
|
0d16feeec1 | ||
|
|
ca48907649 | ||
|
|
9b02c0f1bc | ||
|
|
b25c775994 | ||
|
|
17811adac4 | ||
|
|
e4fcef529e | ||
|
|
f51fe6ee2a | ||
|
|
0f558e66e1 | ||
|
|
7a67fb14f7 | ||
|
|
da3fc317ff | ||
|
|
9b40a1a02e | ||
|
|
aa92c01329 | ||
|
|
99a8a42a15 | ||
|
|
f0ea7add89 | ||
|
|
ac9f322ccb | ||
|
|
e1c2d108e9 | ||
|
|
3aaee0bcf8 | ||
|
|
e3739f1a1f | ||
|
|
0e0a2bd972 | ||
|
|
5c47a411f2 | ||
|
|
16dc257b2f | ||
|
|
4678ea9ecf | ||
|
|
d58963e29d | ||
|
|
c32433284a | ||
|
|
8bb5652df0 | ||
|
|
9ecacf37fe | ||
|
|
f837fbe0d4 | ||
|
|
8e181d6ae2 | ||
|
|
a3a41dca40 | ||
|
|
b78c9715d7 | ||
|
|
520e5bb51d | ||
|
|
586ee4c002 | ||
|
|
3d6c7ed499 | ||
|
|
c3900b7845 | ||
|
|
d369817113 | ||
|
|
d68302ccb3 | ||
|
|
bbd4cc7af6 | ||
|
|
3936277f65 | ||
|
|
aa2a783d1c | ||
|
|
d8d7d64749 | ||
|
|
af3edad2c2 | ||
|
|
5326f800f7 | ||
|
|
b44f9ad9e1 | ||
|
|
b2495300b7 | ||
|
|
eb436263d7 | ||
|
|
0e6385288e | ||
|
|
78f75434fc | ||
|
|
a9c2950419 | ||
|
|
e799f64554 | ||
|
|
95a1daa4ef | ||
|
|
8a5b88c591 | ||
|
|
d3b29cf7d7 | ||
|
|
611522b3e0 | ||
|
|
89d35976d4 | ||
|
|
a7875b26a4 | ||
|
|
4653b8bea1 | ||
|
|
0d0688feb6 | ||
|
|
7a97c14c93 | ||
|
|
cff787524d | ||
|
|
a34a588ee5 | ||
|
|
aa89f2a1ba | ||
|
|
39d275b3e0 | ||
|
|
e22acf98ba | ||
|
|
2e52e35ddc | ||
|
|
f67cb244d2 | ||
|
|
c2319b8dea | ||
|
|
877e43e354 | ||
|
|
6a033a99d3 | ||
|
|
88f7558ddf | ||
|
|
cfe3a298fb | ||
|
|
8e24185f3f | ||
|
|
5fad06d4bd | ||
|
|
df14bc383b | ||
|
|
b8dc1e5371 | ||
|
|
1196dd4c4e | ||
|
|
06ceb3cdb0 | ||
|
|
acaa12f7fc | ||
|
|
0acf384570 | ||
|
|
97a29acfdc | ||
|
|
5eb5ccfdaa | ||
|
|
edcc2c1320 | ||
|
|
68d8ca44fa | ||
|
|
ee9b83d764 | ||
|
|
187066c2cb | ||
|
|
427d74d14f | ||
|
|
061377432e | ||
|
|
53d7548b15 | ||
|
|
e1a4ca0058 | ||
|
|
83ebd6776d | ||
|
|
75dac6fe64 | ||
|
|
e13c023933 | ||
|
|
95183ff8d5 | ||
|
|
64416e0b4d | ||
|
|
1c6837bf92 | ||
|
|
15330ababe | ||
|
|
af70b8150d | ||
|
|
a984e007f4 | ||
|
|
04d8e01f87 | ||
|
|
44e46c5dee | ||
|
|
323e92e636 | ||
|
|
a07fa02ed6 | ||
|
|
d710e1e4b2 | ||
|
|
d35e9b2805 | ||
|
|
cf44463805 | ||
|
|
f59f44692a | ||
|
|
6ba68fab9e | ||
|
|
982addf163 | ||
|
|
dfd89c7691 | ||
|
|
0247a46c47 | ||
|
|
44626c7450 | ||
|
|
c0eb7d689d | ||
|
|
ff7a6aa676 | ||
|
|
dc89439d84 | ||
|
|
7ebc10a4da | ||
|
|
e8e1a5bc08 | ||
|
|
1bb8885f22 | ||
|
|
00d13badcf | ||
|
|
b9ab7b83d2 | ||
|
|
e0f00174c8 | ||
|
|
200a86a6ba | ||
|
|
26622596c8 | ||
|
|
ac789f0855 | ||
|
|
6ecdc05aea | ||
|
|
f8aa50fa49 | ||
|
|
b59bfdef84 | ||
|
|
948ded25f7 | ||
|
|
27f7f2b250 | ||
|
|
184ba2e110 | ||
|
|
f6d8db323b | ||
|
|
301e58f390 | ||
|
|
4cce3067a7 | ||
|
|
4496eff598 | ||
|
|
5e98586354 | ||
|
|
f19295f9a5 | ||
|
|
70546308b4 | ||
|
|
62a425f30e | ||
|
|
4a554dd1ad | ||
|
|
3e6262f21f | ||
|
|
e57a99b2d1 | ||
|
|
8a968ec266 | ||
|
|
b5f00e5c97 | ||
|
|
22e6dd3494 | ||
|
|
08b9f9a2aa | ||
|
|
a24b910096 | ||
|
|
33760042d1 | ||
|
|
2c82fa04e7 | ||
|
|
2b249494d5 | ||
|
|
b56c17d48c | ||
|
|
a6fe08ced9 | ||
|
|
cd1e1410f4 | ||
|
|
353fbf8e9d | ||
|
|
ae05ad4375 | ||
|
|
ff8bbb084c | ||
|
|
89a6802fa0 | ||
|
|
91025e6772 | ||
|
|
d3bc913bcf | ||
|
|
ce56321e1d | ||
|
|
4b6f5410fd | ||
|
|
6544874d3d | ||
|
|
ed90c7f7c9 | ||
|
|
e06fdd1c8b | ||
|
|
b8ffb6b53c | ||
|
|
2dc925ef70 | ||
|
|
639de79c9e | ||
|
|
a9961c821a | ||
|
|
fc082d1a03 | ||
|
|
87fa1ae2a5 | ||
|
|
62490a50f9 | ||
|
|
fcc1a16df4 | ||
|
|
29e0ceb797 | ||
|
|
3e6a951ca1 | ||
|
|
04910f78f1 | ||
|
|
c2977c705c | ||
|
|
4e2137422b | ||
|
|
0696417926 | ||
|
|
f11a433365 | ||
|
|
48a4726d1f | ||
|
|
c927f24b5a | ||
|
|
3cbbf99fec | ||
|
|
03f05f57b7 | ||
|
|
8b483b807b | ||
|
|
816fcfcdbc | ||
|
|
6c28a8795e | ||
|
|
6a57630ae6 | ||
|
|
acba24c4ed | ||
|
|
1e6eb5ec92 | ||
|
|
6871a81ebd | ||
|
|
33053b3b7f | ||
|
|
4040083d5a | ||
|
|
2153b81e6b | ||
|
|
6b1500e681 | ||
|
|
503702cf1f | ||
|
|
7be20b0a87 | ||
|
|
78491f5819 | ||
|
|
39edb88a19 | ||
|
|
fd1a1653b1 | ||
|
|
8d7f0ab28c | ||
|
|
2a2e532564 | ||
|
|
2b1aa01a87 | ||
|
|
06c4e2aea3 | ||
|
|
819bd51ea9 | ||
|
|
6b7f113296 | ||
|
|
a62e9b3a9a | ||
|
|
04685c0bcd | ||
|
|
48fd9f9d80 | ||
|
|
3c578b730f | ||
|
|
7a46886133 | ||
|
|
092049d20f | ||
|
|
f7aced03cd | ||
|
|
eb07281415 | ||
|
|
156b3bdfb5 | ||
|
|
24d8e0a907 | ||
|
|
a1a155849a | ||
|
|
59e67cec3d | ||
|
|
84b18cc3dd | ||
|
|
1092b44947 | ||
|
|
efe28aa5f3 | ||
|
|
16d836c959 | ||
|
|
838ed1abf8 | ||
|
|
d010a55cc0 | ||
|
|
fd1c00a995 | ||
|
|
45aec2db1c | ||
|
|
62b3a1d8dc | ||
|
|
5be9d2f874 | ||
|
|
49a6b1de11 | ||
|
|
ec61a6835d | ||
|
|
ac29fa30a7 | ||
|
|
e966316971 | ||
|
|
ae6efc14d4 | ||
|
|
8ce77f153c | ||
|
|
08638359bb | ||
|
|
7dbade4589 | ||
|
|
8175c4a4d4 | ||
|
|
795ee53340 | ||
|
|
04353db432 | ||
|
|
1f0e511f6e | ||
|
|
7aee5cfab4 | ||
|
|
3477afdef1 | ||
|
|
8b43a82c5f | ||
|
|
bd49e6a4ee | ||
|
|
1dbb9cd38d | ||
|
|
aee7407f79 | ||
|
|
b48de9db84 | ||
|
|
6772f166b7 | ||
|
|
3a0a959383 | ||
|
|
314d7c19cc | ||
|
|
802648eda8 | ||
|
|
5ad5a957a6 | ||
|
|
414d6f895a | ||
|
|
38625a00c3 | ||
|
|
bad688dfa3 | ||
|
|
ec230faa73 |
17
.eslintrc.js
17
.eslintrc.js
@@ -3,7 +3,11 @@ module.exports = {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: ['plugin:react/recommended', 'prettier'],
|
||||
extends: [
|
||||
'plugin:react/recommended',
|
||||
'prettier',
|
||||
'plugin:storybook/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
@@ -12,12 +16,19 @@ module.exports = {
|
||||
ecmaVersion: 12,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'prettier'],
|
||||
plugins: ['@typescript-eslint', 'prettier', 'react-hooks'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'prefer-const': ['warn', { destructuring: 'all' }],
|
||||
'prefer-const': [
|
||||
'warn',
|
||||
{
|
||||
destructuring: 'all',
|
||||
},
|
||||
],
|
||||
'no-var': 'error',
|
||||
eqeqeq: ['error', 'smart'],
|
||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||
'react-hooks/exhaustive-deps': 'off', // Checks effect dependencies
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
|
||||
26
.github/workflows/add_to_project.yml
vendored
Normal file
26
.github/workflows/add_to_project.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Adds all new issues to project board
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@v0.5.0
|
||||
with:
|
||||
project-url: https://github.com/orgs/rive-app/projects/12/views/1
|
||||
github-token: ${{ secrets.ADD_TO_PROJECT_ACTION }}
|
||||
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ["triage"]
|
||||
})
|
||||
85
.github/workflows/publish.yml
vendored
85
.github/workflows/publish.yml
vendored
@@ -1,43 +1,28 @@
|
||||
name: Publish to NPM
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
major:
|
||||
description: 'Major'
|
||||
type: boolean
|
||||
default: false
|
||||
minor:
|
||||
description: 'Minor'
|
||||
type: boolean
|
||||
default: false
|
||||
jobs:
|
||||
determine_version:
|
||||
name: Determine the next build version
|
||||
if: github.event.pull_request.merged == true
|
||||
publish_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.echo_version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
working-directory: ./
|
||||
- name: Git config
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Git config
|
||||
run: |
|
||||
git config --local user.email 'hello@rive.app'
|
||||
git config --local user.name ${{ github.actor }}
|
||||
- name: Authenticate with registry
|
||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
||||
- id: determine_version
|
||||
name: Get Version
|
||||
run: npm run release -- --ci --release-version | tail -n 1 > RELEASE_VERSION
|
||||
working-directory: ./
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
- id: echo_version
|
||||
run: echo "::set-output name=version::$(cat ./RELEASE_VERSION)"
|
||||
|
||||
merge_job:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
needs: [determine_version]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16.x'
|
||||
@@ -50,30 +35,24 @@ jobs:
|
||||
run: npm run lint
|
||||
- name: Run Tests
|
||||
run: npm test
|
||||
- name: Build main dist
|
||||
run: npm run build
|
||||
- name: Copy separated builds
|
||||
run: ./scripts/build.sh
|
||||
- name: Copy package jsons to separate react outputs
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: ./scripts/setup_all_packages.sh
|
||||
- name: Bump Versions of react outputs
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
RELEASE_VERSION: ${{ needs.determine_version.outputs.version }}
|
||||
run: ./scripts/bump_all_versions.sh
|
||||
- name: Git config
|
||||
run: |
|
||||
git config --local user.email 'hello@rive.app'
|
||||
git config --local user.name ${{ github.actor }}
|
||||
- name: Authenticate with registry
|
||||
run: npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
|
||||
- name: Release rive-react
|
||||
- if: ${{ inputs.major == true }}
|
||||
name: Major Release - Bump version number, update changelog, push and tag
|
||||
run: npm run release:major
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
run: npm run release -- --ci
|
||||
- name: Release @rive-app/react-* variants
|
||||
PAT_GITHUB: ${{ secrets.PAT_GITHUB }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- if: ${{inputs.major == false && inputs.minor == true}}
|
||||
name: Minor release - Bump version number, update changelog, push and tag
|
||||
run: npm run release:minor
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
run: ./scripts/publish_all.sh
|
||||
PAT_GITHUB: ${{ secrets.PAT_GITHUB }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- if: ${{inputs.major == false && inputs.minor == false}}
|
||||
name: Patch release - Bump version number, update changelog, push and tag
|
||||
run: npm run release:patch
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||
PAT_GITHUB: ${{ secrets.PAT_GITHUB }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
30
.github/workflows/storybook.yml
vendored
Normal file
30
.github/workflows/storybook.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Deploy Storybook
|
||||
on:
|
||||
# Testing to see if this job is causing the race condition
|
||||
workflow_dispatch:
|
||||
# pull_request:
|
||||
# types: [closed]
|
||||
# branches:
|
||||
# - main
|
||||
# paths: ['src', 'examples/stories/**'] # Trigger the action only when files change in the folders defined here
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install and Build 🔧
|
||||
run: | # Install npm packages and build the Storybook files
|
||||
npm install
|
||||
npm run build-storybook
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@3.6.2
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BRANCH: main # The branch the action should deploy to.
|
||||
FOLDER: docs-build # The folder that the build-storybook script generates files.
|
||||
CLEAN: true # Automatically remove deleted files from the deploy branch
|
||||
TARGET_FOLDER: docs # The folder that we serve our Storybook files from
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"git": {
|
||||
"addUntrackedFiles": true,
|
||||
"requireCleanWorkingDir": false,
|
||||
"commitMessage": "chore: release ${version}",
|
||||
"tagName": "v${version}",
|
||||
"changelog": "npx auto-changelog --stdout --commit-limit false --unreleased --template https://raw.githubusercontent.com/release-it/release-it/master/templates/changelog-compact.hbs"
|
||||
@@ -12,6 +14,12 @@
|
||||
"releaseName": "${version}"
|
||||
},
|
||||
"hooks": {
|
||||
"after:bump": ["npx auto-changelog -p", "git add ./CHANGELOG.md"]
|
||||
"after:version:bump": [
|
||||
"npm run build",
|
||||
"npm run setup-builds",
|
||||
"npm run setup-packages",
|
||||
"npx auto-changelog -p",
|
||||
"npm run publish:all"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1143
CHANGELOG.md
1143
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
78
CONTRIBUTING.md
Normal file
78
CONTRIBUTING.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Contributing
|
||||
|
||||
We love contributions! If you want to run the project locally to test out changes, run the examples, or just see how things work under the hood, read on below.
|
||||
|
||||
## Local development
|
||||
|
||||
This runtime consumes specific tied-down versions of the [JS/WASM runtime](https://github.com/rive-app/rive-wasm) to have better control over changes that occur in that downstream runtime.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the project down
|
||||
2. Run `npm i` in the shell/terminal at the base of the project to install the dependencies needed for the project
|
||||
|
||||
### Local dev server
|
||||
|
||||
To start the local dev server to reflect any changes made to the core `src/` files, run the following in a terminal tab:
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Running the example storybook locally
|
||||
|
||||
We use Storybook to deploy our examples out onto a public-facing page for folks to view and see code examples for. It also serves as the place we'll include any example suites. These story files are stored in `/examples`
|
||||
|
||||
To run Storybook, run the following command in the terminal:
|
||||
|
||||
```
|
||||
npm run storybook
|
||||
```
|
||||
|
||||
To see changes made to the Rive React runtime reflected in your storyook, run the following command in a separate terminal window:
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
We also have a suite of unit tests against the high-level component and various hooks exported in the `test/` folder. When adding new components, changing the API, or underlying functionality, make sure to add a test here!
|
||||
|
||||
To run the test suite:
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
## Making changes
|
||||
|
||||
When you're ready to make changes, push up to a feature branch off of the `main` branch. Create a pull request to this repository in Github. When creating commit messages, please be as descriptive as possible to the changes being made.
|
||||
|
||||
For example, if the change is simply a bug fix or patch change:
|
||||
|
||||
```
|
||||
git commit -m "Fix: Fixing a return type from useRive"
|
||||
```
|
||||
|
||||
Or if it's simply a docs change:
|
||||
|
||||
```
|
||||
git commit -m "Docs: Adding a new link for another example page"
|
||||
```
|
||||
|
||||
For minor/major version releases, also ensure you preface commit messages with:
|
||||
|
||||
```
|
||||
git commit -m "Major: Restructuring the useRive API with new parameters"
|
||||
```
|
||||
|
||||
These messages help make the changelog clear as to what changes are made for future devs to see.
|
||||
|
||||
When pull requests are merged, the runtime will automatically deploy the next release version. By default, patch versions are published. If you want to set the next version as a minor/major version to be released, you have to manually update the `package.json` file at the root of the project to the verison you want it to.
|
||||
|
||||
You can find the deploy scripts in `.github/`
|
||||
|
||||
## Bumping the underlying JS/WASM runtime
|
||||
|
||||
Many times, fixes to the runtime and feature adds come from the underlying JS/WASM runtime. In these cases, just bump the `@rive-app/canvas` and `@rive-app/webgl` versions to the verison you need to incorporate the fix/feature. Run `npm i` and test out the change locally against the Storybook examples and run the test suite to make sure nothing breaks, and then submit a PR with just the `package.json` change if that's all that's needed.
|
||||
217
README.md
217
README.md
@@ -1,208 +1,79 @@
|
||||

|
||||
[](https://rive-app.github.io/rive-react)
|
||||

|
||||

|
||||
|
||||
# Rive React
|
||||
|
||||
React Runtime for [Rive](https://rive.app).
|
||||

|
||||
|
||||
A wrapper around [Rive.js](https://github.com/rive-app/rive-wasm), providing full control over the js runtime while making it super simple to use in React applications.
|
||||
A React runtime library for [Rive](https://rive.app).
|
||||
|
||||
Detailed runtime documentation can be found in [Rive's help center](https://help.rive.app/runtimes).
|
||||
This library is a wrapper around the [JS/Wasm runtime](https://github.com/rive-app/rive-wasm), giving full control over the js runtime while providing components and hooks for React applications.
|
||||
|
||||
## Create and ship interactive animations to any platform
|
||||
## Table of contents
|
||||
|
||||
[Rive](https://rive.app) is a real-time interactive design and animation tool. Use our collaborative editor to create motion graphics that respond to different states and user inputs. Then load your animations into apps, games, and websites with our lightweight open-source runtimes.
|
||||
- :star: [Rive Overview](#rive-overview)
|
||||
- 🚀 [Getting Started & API docs](#getting-started)
|
||||
- :mag: [Supported Versions](#supported-versions)
|
||||
- :books: [Examples](#examples)
|
||||
- :runner: [Migration Guides](#migration-guides)
|
||||
- 👨💻 [Contributing](#contributing)
|
||||
- :question: [Issues](#issues)
|
||||
|
||||
## Installation
|
||||
## Rive overview
|
||||
|
||||
```
|
||||
npm i --save rive-react
|
||||
```
|
||||
[Rive](https://rive.app) is a real-time interactive design and animation tool that helps teams create and run interactive animations anywhere. Designers and developers use our collaborative editor to create motion graphics that respond to different states and user inputs. Our lightweight open-source runtime libraries allow them to load their animations into apps, games, and websites.
|
||||
|
||||
_Note: This library is using React hooks so the minimum version required for both react and react-dom is 16.8.0._
|
||||
:house_with_garden: [Homepage](https://rive.app/)
|
||||
|
||||
## Usage
|
||||
:blue_book: [General help docs](https://rive.app/community/doc/)
|
||||
|
||||
### Component
|
||||
🛠 [Rive Forums](https://rive.app/community/forums/home)
|
||||
|
||||
Rive React provides a basic component as it's default import for displaying simple animations. Note that any animations or state machines instantiated through this component will autoplay.
|
||||
## Getting started
|
||||
|
||||
```js
|
||||
import Rive from 'rive-react';
|
||||
Follow along with the link below for a quick start in getting Rive React integrated into your React apps.
|
||||
|
||||
function Example() {
|
||||
return <Rive src="loader.riv" />;
|
||||
}
|
||||
- [Getting Started with Rive in React](https://rive.app/community/doc/react/docRfaSQ0eaE)
|
||||
- [API documentation](https://rive.app/community/doc/parameters-and-return-values/docJlDMNulDh)
|
||||
|
||||
export default Example;
|
||||
```
|
||||
For more information, see the Runtime sections of the Rive help documentation:
|
||||
|
||||
#### Props
|
||||
- [Animation Playback](https://rive.app/community/doc/animation-playback/docDKKxsr7ko)
|
||||
- [Layout](https://rive.app/community/doc/layout/docBl81zd1GB)
|
||||
- [State Machines](https://rive.app/community/doc/state-machines/docxeznG7iiK)
|
||||
- [Rive Text](https://rive.app/community/doc/text/docn2E6y1lXo)
|
||||
- [Rive Events](https://rive.app/community/doc/rive-events/docbOnaeffgr)
|
||||
- [Loading Assets](https://rive.app/community/doc/loading-assets/doct4wVHGPgC)
|
||||
|
||||
- `src`: File path or URL to the .riv file to display.
|
||||
- `artboard`: _(optional)_ Name to display.
|
||||
- `animations`: _(optional)_ Name or list of names of animtions to play.
|
||||
- `stateMachines`: _(optional)_ Name of state machine to play.
|
||||
- `layout`: _(optional)_ Layout object to define how animations are displayed on the canvas. See [Rive.js](https://github.com/rive-app/rive-wasm#layout) for more details.
|
||||
- _All attributes and eventHandlers that can be passed to a `canvas` element can also be passed to the `Rive` component and used in the same manner._
|
||||
## Supported versions
|
||||
|
||||
#### Styles and Classes
|
||||
|
||||
When rendering out a Rive component, in the DOM, it will show as a `<div>` element that contains the `<canvas>` element that powers the Rive animations. The purpose of the `<div>` element is to help control the sizing of the component. By default, the container has the following styles set on the `style` attribute:
|
||||
```css
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
```
|
||||
|
||||
If you decide to pass in a `className` to the Rive component, you will override these attributes, and you will need to either set these style attributes in your CSS associated with that `className`, or set your own sizing preferences.
|
||||
|
||||
### useRive Hook
|
||||
|
||||
For more advanced usage, the `useRive` hook is provided. The hook will return a component and a [Rive.js](https://github.com/rive-app/rive-wasm) `Rive` object which gives you control of the current rive file.
|
||||
|
||||
```js
|
||||
import { useRive } from 'rive-react';
|
||||
|
||||
function Example() {
|
||||
const params = {
|
||||
src: 'loader.riv',
|
||||
autoplay: false,
|
||||
};
|
||||
const { RiveComponent, rive } = useRive(params);
|
||||
|
||||
return (
|
||||
<RiveComponent
|
||||
onMouseEnter={() => rive && rive.play()}
|
||||
onMouseLeave={() => rive && rive.pause()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Example;
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `riveParams`: Set of parameters that are passed to the Rive.js `Rive` class constructor. `null` and `undefined` can be passed to conditionally display the .riv file.
|
||||
- `opts`: Rive React specific options.
|
||||
|
||||
#### Return Values
|
||||
|
||||
- `RiveComponent`: A Component that can be used to display your .riv file. This component accepts the same attributes and event handlers as a `canvas` element.
|
||||
- `rive`: A Rive.js `Rive` object. This will return as null until the .riv file has fully loaded.
|
||||
- `canvas`: HTMLCanvasElement object, on which the .riv file is rendering.
|
||||
- `setCanvasRef`: A callback ref that can be passed to your own canvas element, if you wish to have control over the rendering of the Canvas element.
|
||||
- `setContainerRef`: A callback ref that can be passed to a container element that wraps the canvas element, if you which to have control over the rendering of the container element.
|
||||
_For the vast majority of use cases, you can just the returned `RiveComponent` and don't need to worry about `setCanvasRef` and `setContainerRef`._
|
||||
|
||||
#### riveParams
|
||||
|
||||
- `src?`: _(optional)_ File path or URL to the .riv file to use. One of `src` or `buffer` must be provided.
|
||||
- `buffer?`: _(optional)_ ArrayBuffer containing the raw bytes from a .riv file. One of `src` or `buffer` must be provided.
|
||||
- `artboard?`: _(optional)_ Name of the artboard to use.
|
||||
- `animations?`: _(optional)_ Name or list of names of animations to play.
|
||||
- `stateMachines?`: _(optional)_ Name of list of names of state machines to load.
|
||||
- `layout?`: _(optional)_ Layout object to define how animations are displayed on the canvas. See [Rive.js](https://github.com/rive-app/rive-wasm#layout) for more details.
|
||||
- `autoplay?`: _(optional)_ If `true`, the animation will automatically start playing when loaded. Defaults to false.
|
||||
- `onLoad?`: _(optional)_ Callback that get's fired when the .rive file loads .
|
||||
- `onLoadError?`: _(optional)_ Callback that get's fired when an error occurs loading the .riv file.
|
||||
- `onPlay?`: _(optional)_ Callback that get's fired when the animation starts playing.
|
||||
- `onPause?`: _(optional)_ Callback that get's fired when the animation pauses.
|
||||
- `onStop?`: _(optional)_ Callback that get's fired when the animation stops playing.
|
||||
- `onLoop?`: _(optional)_ Callback that get's fired when the animation completes a loop.
|
||||
- `onStateChange?`: _(optional)_ Callback that get's fired when a state change occurs.
|
||||
|
||||
#### opts
|
||||
|
||||
- `useDevicePixelRatio`: _(optional)_ If `true`, the hook will scale the resolution of the animation based the [devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio). Defaults to `true`. NOTE: Requires the `setContainerRef` ref callback to be passed to a element wrapping a canvas element. If you use the `RiveComponent`, then this will happen automatically.
|
||||
- `fitCanvasToArtboardHeight`: _(optional)_ If `true`, then the canvas will resize based on the height of the artboard. Defaults to `false`.
|
||||
- `useOffscreenRenderer`: _(optional)_ If `true`, the Rive instance will share (or create if one does not exist) an offscreen `WebGL` context. This allows you to display multiple Rive animations on one screen to work around some browser limitations regarding multiple concurrent WebGL contexts. If `false`, each Rive instance will have its own dedicated `WebGL` context, and you may need to be cautious of the browser limitations just mentioned. Defaults to `true`.
|
||||
|
||||
### useStateMachineInput Hook
|
||||
|
||||
The `useStateMachineInput` hook is provided to make it easier to interact with state machine inputs on a rive file.
|
||||
|
||||
```js
|
||||
import { useRive, useStateMachineInput } from 'rive-react';
|
||||
|
||||
function Example() {
|
||||
const STATE_MACHINE_NAME = 'button';
|
||||
const INPUT_NAME = 'onClick';
|
||||
|
||||
const { RiveComponent, rive } = useRive({
|
||||
src: 'button.riv',
|
||||
stateMachines: STATE_MACHINE_NAME,
|
||||
autoplay: true,
|
||||
});
|
||||
|
||||
const onClickInput = useStateMachineInput(
|
||||
rive,
|
||||
STATE_MACHINE_NAME,
|
||||
INPUT_NAME
|
||||
);
|
||||
|
||||
// This example is using a state machine with a trigger input.
|
||||
return <RiveComponent onClick={() => onClickInput.fire()} />;
|
||||
}
|
||||
|
||||
export default Example;
|
||||
```
|
||||
|
||||
See our [examples](examples) folder for working examples of [Boolean](examples/state-machine-boolean-input) and [Number](examples/state-machine-number-input) inputs.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `rive`: A `Rive` object. This is returned by the `useRive` hook.
|
||||
- `stateMachineName`: Name of the state machine.
|
||||
- `inputName`: Name of the state machine input.
|
||||
- `initialValue`: Initial value to set on a state machine input when it's loaded in, for number or boolean inputs. **Note** that this may trigger any transitional animations between the initial state and any next states that depend on the input this `initialValue` is being set to. If this is problematic or conflicting for your case, we recommend setting the true initial value of the input on your state machine in the Rive editor.
|
||||
|
||||
#### Return Value
|
||||
|
||||
A Rive.js `stateMachineInput` object.
|
||||
This library supports React versions `^16.8.0` through `^18.0.0`.
|
||||
|
||||
## Examples
|
||||
|
||||
The [examples](examples) shows a number of different ways to use Rive React. See the instructions for each example to run locally.
|
||||
Check out our Storybook instance that shows how to use the library in small examples, along with code snippets! This includes examples using the basic component, as well as the convenient hooks exported to take advantage of state machines.
|
||||
|
||||
- [Example page](https://rive-app.github.io/rive-react)
|
||||
- [Login screen w/ input tracking](https://rive-app.github.io/rive-use-cases/?path=/story/example-loginformcomponent--primary)
|
||||
- [Mouse tracking](https://codesandbox.io/s/rive-mouse-track-test-t0y965?file=/src/App.js)
|
||||
- [Accessibility concerns](https://rive.app/blog/accesible-web-animations-aria-live-regions)
|
||||
|
||||
## Migration notes
|
||||
### Awesome Rive
|
||||
|
||||
### Migrating from version 0.0.x to 1.x.x
|
||||
For even more examples and resources on using Rive at runtime or in other tools, checkout the [awesome-rive](https://github.com/rive-app/awesome-rive) repo.
|
||||
|
||||
Starting in v 1.0.0, we've migrated from wrapping around the `@rive-app/canvas` runtime (which uses the `CanvasRendereringContext2D` renderer) to the `@rive-app/webgl` runtime (which uses the WebGL renderer). The high-level API doesn't require any change to upgrade, but there are some notes to consider about the backing renderer.
|
||||
## Migration guides
|
||||
|
||||
The backing `WebGL` runtime allows for best performance across all devices, as well as support for some features that are not supported in the `canvas` renderer runtime. To allow the `react` runtime to support some of the newer features in Rive, we needed to switch the `rive-react` backing runtime to `@rive-app/webgl`.
|
||||
Using an older version of the runtime and need to learn how to upgrade to the latest version? Check out the migration guides below in our help center that help guide you through version bumps; breaking changes and all!
|
||||
|
||||
One note about this switch is that some browsers may limit the number of concurrent WebGL contexts. For example, Chrome may only support up to 16 contexts concurrently. We pass a property called `useOffscreenRenderer` set to true to the backing runtime when instantiating Rive by default, which helps to manage the lifecycle of the `canvas` with a single offscreen `WebGL` context, even if there are many Rive animations on the screen (i.e 16+). If you need a single `WebGL` context per Rive animation/instance, pass in the `useOffscreenRenderer` property set to `false` in the `useRive` options, or as a prop in the default export component from this runtime. See below for an example:
|
||||
[Migration guides](https://rive.app/community/doc/migrating-from-v3-to-v4/dociIPXVHKFF)
|
||||
|
||||
```js
|
||||
const {rive, RiveComponent} = useRive({
|
||||
src: 'foo.riv',
|
||||
}, {
|
||||
// Default (you don't need to set this)
|
||||
useOffscreenRenderer: true,
|
||||
// To override and use one context per Rive instance, uncomment and use the line below
|
||||
// useOffscreenRenderer: false,
|
||||
});
|
||||
## Contributing
|
||||
|
||||
// or you can override the flag in JSX via props
|
||||
return (
|
||||
<Rive src="foo.riv" useOffscreenRenderer={false} />
|
||||
);
|
||||
```
|
||||
We love contributions! Check out our [contributing docs](./CONTRIBUTING.md) to get more details into how to run this project, the examples, and more all locally.
|
||||
|
||||
### Migrating from version 1.x.x to 2.x.x
|
||||
## Issues
|
||||
|
||||
#### Package split
|
||||
|
||||
In most cases, you may be able to migrate safely. We are mainly enabling the React runtime to work with both backing renderers `@rive-app/webgl` and `@rive-app/canvas`, such that you can use either `@rive-app/react-canvas` or `@rive-app/react-webgl` as the dependency in your React applications. Another change that is mostly internal is that by default, `rive-react` will now use `@rive-app/canvas` (as opposed to `@rive-app/webgl`) to wrap around, as it currently yields the fastest performance across devices. Therefore, **we recommend installing `@rive-app/react-canvas` in your applicaions**. However, if you need a WebGL backing renderer, you may want to use `@rive-app/react-webgl`.
|
||||
|
||||
#### Classes, styles, and component props
|
||||
Starting in v2.0, we introduce one breaking change where any non-style props set on the `RiveComponent` (i.e `aria-*`, `role`, etc.) will be set on the inner `<canvas>` element. Previously, all extra props would be set onto the containing `<div>` element. Both the `className` and `style` props will continue to be set on the `<div>` element that wraps the canvas, as this dictates the sizing of the Rive component.
|
||||
|
||||
|
||||
### Migrating to 3.0
|
||||
|
||||
There are no breaking changes here. If you have migrated to v2.x.x, you can safely migrate to 3.0.
|
||||
Have an issue with using the runtime, or want to suggest a feature/API to help make your development life better? Log an issue in our [issues](https://github.com/rive-app/rive-react/issues) tab! You can also browse older issues and discussion threads there to see solutions that may have worked for common problems.
|
||||
|
||||
@@ -21,3 +21,5 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
*storybook.log
|
||||
74
examples/.storybook/main.ts
Normal file
74
examples/.storybook/main.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
import path from 'path';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/preset-create-react-app',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/react-webpack5',
|
||||
options: {},
|
||||
},
|
||||
staticDirs: ['../public'],
|
||||
webpackFinal: async (config) => {
|
||||
if (!config.resolve) config.resolve = {};
|
||||
if (!config.resolve.alias) config.resolve.alias = {};
|
||||
|
||||
config.resolve.alias['react'] = path.resolve(
|
||||
__dirname,
|
||||
'../../node_modules/react'
|
||||
);
|
||||
config.resolve.alias['react-dom'] = path.resolve(
|
||||
__dirname,
|
||||
'../../node_modules/react-dom'
|
||||
);
|
||||
|
||||
config.resolve.alias['@rive-app/react-canvas'] = path.resolve(
|
||||
__dirname,
|
||||
'../../'
|
||||
);
|
||||
config.resolve.alias['@rive-app/react-canvas-lite'] = path.resolve(
|
||||
__dirname,
|
||||
'../../'
|
||||
);
|
||||
config.resolve.alias['@rive-app/react-webgl'] = path.resolve(
|
||||
__dirname,
|
||||
'../../'
|
||||
);
|
||||
config.resolve.alias['@rive-app/react-webgl2'] = path.resolve(
|
||||
__dirname,
|
||||
'../../'
|
||||
);
|
||||
|
||||
config.module?.rules?.push({
|
||||
test: /\.(ts|tsx|js|jsx)$/,
|
||||
include: [
|
||||
path.resolve(__dirname, '../src'),
|
||||
path.resolve(__dirname, '../../'),
|
||||
],
|
||||
use: {
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
presets: [
|
||||
require.resolve('@babel/preset-env'),
|
||||
require.resolve('@babel/preset-react'),
|
||||
require.resolve('@babel/preset-typescript'),
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
config.watchOptions = {
|
||||
ignored: /node_modules/,
|
||||
poll: 1000,
|
||||
aggregateTimeout: 300,
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
16
examples/.storybook/preview.ts
Normal file
16
examples/.storybook/preview.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Preview } from '@storybook/react';
|
||||
|
||||
import './stories.css';
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
7
examples/.storybook/stories.css
Normal file
7
examples/.storybook/stories.css
Normal file
@@ -0,0 +1,7 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#storybook-root {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
# Basic Typescript
|
||||
|
||||
This is an example app that uses the useRive hook with typescript.
|
||||
|
||||
## To Run
|
||||
|
||||
This example is created using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html).
|
||||
|
||||
To install and run:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "basic-typescript",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^12.20.14",
|
||||
"@types/react": "^17.0.9",
|
||||
"@types/react-dom": "^17.0.6",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"rive-react": "latest",
|
||||
"typescript": "^4.3.2",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<title>React App - Basic Typescript</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
@@ -1,19 +0,0 @@
|
||||
import { useRive, UseRiveParameters } from 'rive-react';
|
||||
|
||||
function App() {
|
||||
const params: UseRiveParameters = {
|
||||
src: 'poison-loader.riv',
|
||||
autoplay: true,
|
||||
};
|
||||
|
||||
const { RiveComponent } = useRive(params);
|
||||
|
||||
return (
|
||||
// The animation will fit to the parent element.
|
||||
<div style={{ height: '500px', width: '500px' }}>
|
||||
<RiveComponent />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
/// <reference types="react-scripts" />
|
||||
@@ -1,14 +0,0 @@
|
||||
# Basic with hook
|
||||
|
||||
This is a very basic example of using the useRive to autoplay a simple looping animation.
|
||||
|
||||
## To Run
|
||||
|
||||
This example is created using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html).
|
||||
|
||||
To install and run:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "basic-with-hook",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"react": "file:../../node_modules/react",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"rive-react": "file:../..",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<title>Rive React - Basic with Hook</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,29 +0,0 @@
|
||||
import { useRive } from 'rive-react';
|
||||
|
||||
function App() {
|
||||
const params = {
|
||||
src: 'poison-loader.riv',
|
||||
autoplay: true,
|
||||
};
|
||||
|
||||
const { RiveComponent: RiveComponentBasic } = useRive(params);
|
||||
|
||||
const { RiveComponent: RiveComponentTouch } = useRive({
|
||||
src: 'magic-ball.riv',
|
||||
autoplay: true,
|
||||
stateMachines: "Main State Machine",
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ height: '500px', width: '500px' }}>
|
||||
<RiveComponentBasic />
|
||||
</div>
|
||||
<div style={{ height: '300px', width: '300px' }}>
|
||||
<RiveComponentTouch />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
@@ -1,14 +0,0 @@
|
||||
# Basic
|
||||
|
||||
This is a very basic example of using the Rive component to autoplay a simple looping animation.
|
||||
|
||||
## To Run
|
||||
|
||||
This example is created using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html).
|
||||
|
||||
To install and run:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "basic-example",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"react": "file:../../node_modules/react",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"rive-react": "file:../..",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<title>Rive React - Basic</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
@@ -1,12 +0,0 @@
|
||||
import Rive from 'rive-react';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
// The animation will fit to the parent element.
|
||||
<div style={{ height: '500px', width: '500px' }}>
|
||||
<Rive src="poison-loader.riv" autoplay />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
54
examples/package.json
Normal file
54
examples/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "examples",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.126",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest",
|
||||
"plugin:storybook/recommended"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^8.6.12",
|
||||
"@storybook/addon-interactions": "^8.6.12",
|
||||
"@storybook/addon-onboarding": "^8.6.12",
|
||||
"@storybook/blocks": "^8.6.12",
|
||||
"@storybook/preset-create-react-app": "^8.6.12",
|
||||
"@storybook/react": "^8.6.12",
|
||||
"@storybook/react-webpack5": "^8.6.12",
|
||||
"@storybook/test": "^8.6.12",
|
||||
"eslint-plugin-storybook": "^0.12.0",
|
||||
"storybook": "^8.6.12",
|
||||
"webpack": "^5.99.6"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
# Play on hover
|
||||
|
||||
This provides an example of how to play/pause a Rive animation when the mouse is hovered over it.
|
||||
|
||||
## To Run
|
||||
|
||||
This example is created using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html).
|
||||
|
||||
To install and run:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "play-on-hover",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"rive-react": "latest",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<title>Rive React - Play on hover</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
@@ -1,32 +0,0 @@
|
||||
import { useRive } from 'rive-react';
|
||||
|
||||
function App() {
|
||||
const params = {
|
||||
src: 'poison-loader.riv',
|
||||
autoplay: false,
|
||||
};
|
||||
|
||||
const { RiveComponent, rive } = useRive(params);
|
||||
|
||||
function onMouseEnter() {
|
||||
// `rive` will return as null until the file as fully loaded, so we include this
|
||||
// guard to prevent any unwanted errors.
|
||||
if (rive) {
|
||||
rive.play();
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseLeave() {
|
||||
if (rive) {
|
||||
rive.pause();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: '600px', width: '600px' }}>
|
||||
<RiveComponent onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
@@ -1,14 +0,0 @@
|
||||
# Play/Pause Buttons
|
||||
|
||||
This example shows how we can play/pause the Rive animation on a button click and update the text of the button based on events happening in the runtime.
|
||||
|
||||
## To Run
|
||||
|
||||
This example is created using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html).
|
||||
|
||||
To install and run:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "play-pause-button",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"rive-react": "latest",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<title>Rive React - Play/Pause Button</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
@@ -1,50 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRive } from 'rive-react';
|
||||
|
||||
function App() {
|
||||
const [buttonText, setButtonText] = useState('Pause');
|
||||
const { RiveComponent, rive } = useRive({
|
||||
src: 'poison-loader.riv',
|
||||
autoplay: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (rive) {
|
||||
// "play" event is fired when the animation starts to play, so we update
|
||||
// button text on this event.
|
||||
rive.on('play', () => {
|
||||
setButtonText('Pause');
|
||||
});
|
||||
|
||||
// As above, the "pause" event is fired when the animation pauses.
|
||||
rive.on('pause', () => {
|
||||
setButtonText('Play');
|
||||
});
|
||||
}
|
||||
// We listen for changes to the rive object. The rive object will be null
|
||||
// until the rive file has loaded.
|
||||
}, [rive]);
|
||||
|
||||
function onButtonClick() {
|
||||
// `rive` will return as null until the file as fully loaded, so we include this
|
||||
// guard to prevent any unwanted errors.
|
||||
if (rive) {
|
||||
if (rive.isPlaying) {
|
||||
rive.pause();
|
||||
} else {
|
||||
rive.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
// The animation will fit to the parent element, so we set a large height
|
||||
// and width for this example.
|
||||
<div style={{ height: '500px', width: '500px' }}>
|
||||
<RiveComponent />
|
||||
<button onClick={onButtonClick}>{buttonText}</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
BIN
examples/public/avatars.riv
Normal file
BIN
examples/public/avatars.riv
Normal file
Binary file not shown.
BIN
examples/public/layout_test.riv
Normal file
BIN
examples/public/layout_test.riv
Normal file
Binary file not shown.
BIN
examples/public/rating.riv
Normal file
BIN
examples/public/rating.riv
Normal file
Binary file not shown.
17
examples/src/components/Events.stories.ts
Normal file
17
examples/src/components/Events.stories.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import Events from './Events';
|
||||
|
||||
const meta = {
|
||||
title: 'Events',
|
||||
component: Events,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
args: {},
|
||||
} satisfies Meta<typeof Events>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
34
examples/src/components/Events.tsx
Normal file
34
examples/src/components/Events.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useRive, EventType, RiveEventType } from '@rive-app/react-canvas';
|
||||
|
||||
const Events = () => {
|
||||
const { rive, RiveComponent } = useRive({
|
||||
src: 'rating.riv',
|
||||
stateMachines: 'State Machine 1',
|
||||
autoplay: true,
|
||||
automaticallyHandleEvents: true,
|
||||
});
|
||||
|
||||
const onRiveEventReceived = (riveEvent: any) => {
|
||||
console.log('Rive event received:', riveEvent);
|
||||
const eventData = riveEvent.data;
|
||||
const eventProperties = eventData.properties;
|
||||
if (eventData.type === RiveEventType.General) {
|
||||
console.log('Event name', eventData.name);
|
||||
console.log('Rating', eventProperties.rating);
|
||||
console.log('Message', eventProperties.message);
|
||||
}
|
||||
};
|
||||
|
||||
// Wait until the rive object is instantiated before adding the Rive
|
||||
// event listener
|
||||
useEffect(() => {
|
||||
if (rive) {
|
||||
rive.on(EventType.RiveEvent, onRiveEventReceived);
|
||||
}
|
||||
}, [rive]);
|
||||
|
||||
return <RiveComponent />;
|
||||
};
|
||||
|
||||
export default Events;
|
||||
17
examples/src/components/Http.stories.ts
Normal file
17
examples/src/components/Http.stories.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import Http from './Http';
|
||||
|
||||
const meta = {
|
||||
title: 'Http',
|
||||
component: Http,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
args: {},
|
||||
} satisfies Meta<typeof Http>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
14
examples/src/components/Http.tsx
Normal file
14
examples/src/components/Http.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { useRive } from '@rive-app/react-canvas';
|
||||
|
||||
const Http = () => {
|
||||
const { RiveComponent } = useRive({
|
||||
src: 'https://cdn.rive.app/animations/vehicles.riv',
|
||||
stateMachines: 'bumpy',
|
||||
autoplay: true,
|
||||
});
|
||||
|
||||
return <RiveComponent />;
|
||||
};
|
||||
|
||||
export default Http;
|
||||
17
examples/src/components/ResponsiveLayout.stories.ts
Normal file
17
examples/src/components/ResponsiveLayout.stories.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import ResponsiveLayout from './ResponsiveLayout';
|
||||
|
||||
const meta = {
|
||||
title: 'ResponsiveLayout',
|
||||
component: ResponsiveLayout,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
args: {},
|
||||
} satisfies Meta<typeof ResponsiveLayout>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
18
examples/src/components/ResponsiveLayout.tsx
Normal file
18
examples/src/components/ResponsiveLayout.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Fit, useRive, Layout } from '@rive-app/react-canvas';
|
||||
|
||||
const ResponsiveLayout = () => {
|
||||
const { RiveComponent } = useRive({
|
||||
src: 'layout_test.riv',
|
||||
artboard: 'Artboard',
|
||||
stateMachines: 'State Machine 1',
|
||||
autoplay: true,
|
||||
layout: new Layout({
|
||||
fit: Fit.Layout,
|
||||
}),
|
||||
});
|
||||
|
||||
return <RiveComponent />;
|
||||
};
|
||||
|
||||
export default ResponsiveLayout;
|
||||
17
examples/src/components/Simple.stories.ts
Normal file
17
examples/src/components/Simple.stories.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import Simple from './Simple';
|
||||
|
||||
const meta = {
|
||||
title: 'Simple',
|
||||
component: Simple,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
args: {},
|
||||
} satisfies Meta<typeof Simple>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
14
examples/src/components/Simple.tsx
Normal file
14
examples/src/components/Simple.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { useRive } from '@rive-app/react-canvas';
|
||||
|
||||
const Simple = () => {
|
||||
const { RiveComponent } = useRive({
|
||||
src: 'avatars.riv',
|
||||
artboard: 'Avatar 3',
|
||||
autoplay: true,
|
||||
});
|
||||
|
||||
return <RiveComponent />;
|
||||
};
|
||||
|
||||
export default Simple;
|
||||
@@ -1,14 +0,0 @@
|
||||
# State Machine Boolean Input
|
||||
|
||||
This example shows how to interact with a state machine, using various onMouse\* callbacks to trigger transations.
|
||||
|
||||
## To Run
|
||||
|
||||
This example is created using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html).
|
||||
|
||||
To install and run:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "state-machine-boolean-input",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"rive-react": "latest",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<title>Rive React - State Machine Boolean Input</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
@@ -1,58 +0,0 @@
|
||||
import { useRive, useStateMachineInput } from 'rive-react';
|
||||
|
||||
function App() {
|
||||
const STATE_MACHINE_NAME = 'State Machine 1';
|
||||
const ON_HOVER_INPUT_NAME = 'Hover';
|
||||
const ON_PRESSED_INPUT_NAME = 'Pressed';
|
||||
|
||||
const { RiveComponent, rive } = useRive({
|
||||
src: 'like.riv',
|
||||
stateMachines: STATE_MACHINE_NAME,
|
||||
artboard: 'New Artboard',
|
||||
autoplay: true,
|
||||
});
|
||||
|
||||
// Both onHoverInput and onPressedInput are boolean inputs. To transition
|
||||
// states we need to set the value property to true or false.
|
||||
const onHoverInput = useStateMachineInput(
|
||||
rive,
|
||||
STATE_MACHINE_NAME,
|
||||
ON_HOVER_INPUT_NAME
|
||||
);
|
||||
const onPressedInput = useStateMachineInput(
|
||||
rive,
|
||||
STATE_MACHINE_NAME,
|
||||
ON_PRESSED_INPUT_NAME
|
||||
);
|
||||
|
||||
function onMouseEnter() {
|
||||
onHoverInput.value = true;
|
||||
}
|
||||
|
||||
function onMouseLeave() {
|
||||
onHoverInput.value = false;
|
||||
}
|
||||
|
||||
function onMouseDown() {
|
||||
onPressedInput.value = true;
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
onPressedInput.value = false;
|
||||
}
|
||||
|
||||
return (
|
||||
// The animation will fit to the parent element, so we set a large height
|
||||
// and width for this example.
|
||||
<div style={{ height: '500px', width: '500px' }}>
|
||||
<RiveComponent
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
@@ -1,14 +0,0 @@
|
||||
# State Machine Number Input
|
||||
|
||||
This example shows how to interact with a state machine with number inputs. We trigger the transistions by changing the value of a number input.
|
||||
|
||||
## To Run
|
||||
|
||||
This example is created using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html).
|
||||
|
||||
To install and run:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "state-machine-number-input",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"rive-react": "latest",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<title>Rive React - State Machine Number Input</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
@@ -1,34 +0,0 @@
|
||||
import { useRive, useStateMachineInput } from 'rive-react';
|
||||
|
||||
function App() {
|
||||
const STATE_MACHINE_NAME = 'State Machine ';
|
||||
const INPUT_NAME = 'Level';
|
||||
|
||||
const { RiveComponent, rive } = useRive({
|
||||
src: 'skills.riv',
|
||||
stateMachines: STATE_MACHINE_NAME,
|
||||
artboard: 'New Artboard',
|
||||
autoplay: true,
|
||||
});
|
||||
|
||||
// levelInput is a number state machine input. To transition the state machine,
|
||||
// we need to set the value to a number. For this state machine input, we need
|
||||
// to set to 0, 1 or 2 for a state transition to occur.
|
||||
const levelInput = useStateMachineInput(rive, STATE_MACHINE_NAME, INPUT_NAME);
|
||||
|
||||
return (
|
||||
// The animation will fit to the parent element, so we set a large height
|
||||
// and width for this example.
|
||||
<div style={{ height: '500px', width: '500px' }}>
|
||||
<RiveComponent />
|
||||
<div>
|
||||
Level:
|
||||
<button onClick={() => (levelInput.value = 0)}>0</button>
|
||||
<button onClick={() => (levelInput.value = 1)}>1</button>
|
||||
<button onClick={() => (levelInput.value = 2)}>2</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
@@ -1,14 +0,0 @@
|
||||
# State Machine Trigger Input
|
||||
|
||||
This example shows how to interact with a state machine with a trigger input. We call the `fire()` function on the state machine input to transition the state when the animation is clicked.
|
||||
|
||||
## To Run
|
||||
|
||||
This example is created using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html).
|
||||
|
||||
To install and run:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "state-machine-trigger-input",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"rive-react": "latest",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<title>Rive React - State Machine Trigger Input</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
@@ -1,31 +0,0 @@
|
||||
import { useRive, useStateMachineInput } from 'rive-react';
|
||||
|
||||
function App() {
|
||||
const STATE_MACHINE_NAME = 'State Machine 1';
|
||||
const INPUT_NAME = 'Pressed';
|
||||
|
||||
const { RiveComponent, rive } = useRive({
|
||||
src: 'piggy-bank.riv',
|
||||
stateMachines: STATE_MACHINE_NAME,
|
||||
artboard: 'New Artboard',
|
||||
autoplay: true,
|
||||
});
|
||||
|
||||
// pressedInput in a trigger state machine input. To transition the state
|
||||
// we need to call the `fire()` method on the input.
|
||||
const pressedInput = useStateMachineInput(
|
||||
rive,
|
||||
STATE_MACHINE_NAME,
|
||||
INPUT_NAME
|
||||
);
|
||||
|
||||
return (
|
||||
// The animation will fit to the parent element, so we set a large height
|
||||
// and width for this example.
|
||||
<div style={{ height: '500px', width: '500px' }}>
|
||||
<RiveComponent onClick={() => pressedInput.fire()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
@@ -6,6 +6,12 @@
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"paths": {
|
||||
"@rive-app/react-canvas": ["../"],
|
||||
"@rive-app/react-webgl": ["../"],
|
||||
"@rive-app/react-webgl2": ["../"],
|
||||
"@rive-app/react-canvas-lite": ["../"]
|
||||
},
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
10773
examples/yarn.lock
Normal file
10773
examples/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
9
npm/react-canvas-lite/README.md
Normal file
9
npm/react-canvas-lite/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# @rive-app/react-canvas-lite
|
||||
|
||||
Output for `rive-react` using the backing `@rive-app/canvas-lite` JS runtime.
|
||||
|
||||
## Why Lite?
|
||||
|
||||
The current `@rive-app/react-canvas` dependency supports all Rive features and contains the necessary backing dependencies to render those graphics. This `lite` version has the same API, but does not compile and build with certain dependencies in order to keep the package size as small as possible.
|
||||
|
||||
At this time, this lite version of `@rive-app/react-canvas-lite` will not render Rive Text onto the canvas or play Rive Audio. Note however, that even if your Rive file may include Rive Text components, rendering the graphic should not cause any app errors, or cease to render. The same is true for playing audio.
|
||||
26
npm/react-canvas-lite/package.json
Normal file
26
npm/react-canvas-lite/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@rive-app/react-canvas-lite",
|
||||
"version": "4.19.1",
|
||||
"description": "React wrapper around the @rive-app/canvas-lite library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rive-app/rive-react.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/rive-app/rive-react/issues"
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/canvas-lite": "2.27.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
}
|
||||
}
|
||||
26
npm/react-canvas/package.json
Normal file
26
npm/react-canvas/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@rive-app/react-canvas",
|
||||
"version": "4.19.1",
|
||||
"description": "React wrapper around the @rive-app/canvas library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rive-app/rive-react.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/rive-app/rive-react/issues"
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/canvas": "2.27.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
}
|
||||
}
|
||||
26
npm/react-webgl/package.json
Normal file
26
npm/react-webgl/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@rive-app/react-webgl",
|
||||
"version": "4.19.1",
|
||||
"description": "React wrapper around the @rive-app/webgl library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rive-app/rive-react.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/rive-app/rive-react/issues"
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/webgl": "2.27.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
}
|
||||
}
|
||||
0
npm/react-webgl2/README.md
Normal file
0
npm/react-webgl2/README.md
Normal file
26
npm/react-webgl2/package.json
Normal file
26
npm/react-webgl2/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@rive-app/react-webgl2",
|
||||
"version": "4.19.1",
|
||||
"description": "React wrapper around the @rive-app/webgl2 library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist/**"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rive-app/rive-react.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/rive-app/rive-react/issues"
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/webgl2": "2.27.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
}
|
||||
}
|
||||
35
package.json
35
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "rive-react",
|
||||
"version": "3.0.6",
|
||||
"version": "4.19.1",
|
||||
"description": "React wrapper around the rive-js library",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
@@ -14,7 +14,15 @@
|
||||
"lint": "eslint -c .eslintrc.js 'src/**/*{.ts,.tsx}'",
|
||||
"format": "prettier --write src",
|
||||
"types:check": "tsc --noEmit",
|
||||
"release": "release-it"
|
||||
"release": "release-it",
|
||||
"release:patch": "npm run release -- --ci",
|
||||
"release:minor": "npm run release -- minor --ci",
|
||||
"release:major": "npm run release -- major --ci",
|
||||
"setup-builds": "./scripts/build.sh",
|
||||
"setup-packages": "./scripts/setup_all_packages.sh",
|
||||
"bump-versions": "./scripts/bump_all_versions.sh $npm_package_version",
|
||||
"publish:all": "./scripts/publish_all.sh --access public",
|
||||
"storybook": "yarn --cwd examples storybook"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -27,24 +35,27 @@
|
||||
},
|
||||
"homepage": "https://github.com/rive-app/rive-react#readme",
|
||||
"dependencies": {
|
||||
"@rive-app/canvas": "1.0.65",
|
||||
"@rive-app/webgl": "1.0.62"
|
||||
"@rive-app/canvas": "2.27.2",
|
||||
"@rive-app/canvas-lite": "2.27.2",
|
||||
"@rive-app/webgl": "2.27.2",
|
||||
"@rive-app/webgl2": "2.27.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.0",
|
||||
"@testing-library/jest-dom": "^5.13.0",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/react-hooks": "^7.0.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/offscreencanvas": "^2019.6.4",
|
||||
"@types/react": "^17.0.9",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/testing-library__jest-dom": "^5.9.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
||||
"@typescript-eslint/parser": "^5.7.0",
|
||||
"auto-changelog": "^2.3.0",
|
||||
"bunchee": "1.8.3",
|
||||
"babel-loader": "^8.2.5",
|
||||
"bunchee": "1.8.5",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
@@ -53,10 +64,12 @@
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-react": "^7.27.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-storybook": "^0.5.12",
|
||||
"jest": "^27.0.4",
|
||||
"prettier": "^2.3.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"release-it": "^14.10.0",
|
||||
"ts-jest": "^27.1.1",
|
||||
"typescript": "^4.5.4",
|
||||
|
||||
@@ -5,6 +5,8 @@ set -e
|
||||
# Copy the build to each react-variant build for npm release
|
||||
cp -r ./dist ./npm/react-webgl
|
||||
cp -r ./dist ./npm/react-canvas
|
||||
cp -r ./dist ./npm/react-canvas-lite
|
||||
cp -r ./dist ./npm/react-webgl2
|
||||
|
||||
echo "Replacing the canvas with webgl references in react-webgl"
|
||||
pushd ./npm/react-webgl/dist
|
||||
@@ -16,3 +18,25 @@ else
|
||||
find . -type f -name "*.js" -print0 | xargs -0 sed -i -e 's/@rive-app\/canvas/@rive-app\/webgl/g'
|
||||
fi
|
||||
popd
|
||||
|
||||
echo "Replacing the canvas with webgl2 references in react-webgl2"
|
||||
pushd ./npm/react-webgl2/dist
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
find . -type f -name "*.ts" -print0 | xargs -0 sed -i '' -e 's/@rive-app\/canvas/@rive-app\/webgl2/g'
|
||||
find . -type f -name "*.js" -print0 | xargs -0 sed -i '' -e 's/@rive-app\/canvas/@rive-app\/webgl2/g'
|
||||
else
|
||||
find . -type f -name "*.ts" -print0 | xargs -0 sed -i -e 's/@rive-app\/canvas/@rive-app\/webgl2/g'
|
||||
find . -type f -name "*.js" -print0 | xargs -0 sed -i -e 's/@rive-app\/canvas/@rive-app\/webgl2/g'
|
||||
fi
|
||||
popd
|
||||
|
||||
echo "Replacing the canvas with canvas-lite references in react-canvas-lite"
|
||||
pushd ./npm/react-canvas-lite/dist
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
find . -type f -name "*.ts" -print0 | xargs -0 sed -i '' -e 's/@rive-app\/canvas/@rive-app\/canvas-lite/g'
|
||||
find . -type f -name "*.js" -print0 | xargs -0 sed -i '' -e 's/@rive-app\/canvas/@rive-app\/canvas-lite/g'
|
||||
else
|
||||
find . -type f -name "*.ts" -print0 | xargs -0 sed -i -e 's/@rive-app\/canvas/@rive-app\/canvas-lite/g'
|
||||
find . -type f -name "*.js" -print0 | xargs -0 sed -i -e 's/@rive-app\/canvas/@rive-app\/canvas-lite/g'
|
||||
fi
|
||||
popd
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Bump the version number of every npm module in the npm folder.
|
||||
for dir in ./npm/*; do
|
||||
pushd $dir > /dev/null
|
||||
repo_name=`echo $dir | sed 's:.*/::' | sed 's/_/-/g'`
|
||||
echo Bumping version of $repo_name
|
||||
../../scripts/bump_version.sh $repo_name $RELEASE_VERSION
|
||||
popd > /dev/null
|
||||
done
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Bumps the version of a single npm module found in the current working
|
||||
# directory. Call bump_version.sh from the path with package.json in it.
|
||||
|
||||
set -e
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
# RELEASE_VERSION will come from an env variable passed in from the GH action workflow
|
||||
node $SCRIPT_DIR/nextVersion.js "$RELEASE_VERSION" `pwd`
|
||||
|
||||
# Replace the dist package json with the newly trimmed one
|
||||
cp -f ./package.json ./dist/package.json
|
||||
@@ -1,33 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = process.argv[3];
|
||||
const package = require(path + '/package.json');
|
||||
const currentVersion = package.version;
|
||||
const nextVersion = process.argv[2].trim().replace(/\'/g, '"');
|
||||
|
||||
if (!nextVersion || nextVersion === currentVersion) {
|
||||
throw new Error('Next version is not defined or is a version that already exists');
|
||||
}
|
||||
|
||||
// Returns -1 if first is less than second, 1 if first is greater than second, otherwise 0 if equal.
|
||||
function compareVersion(first, second) {
|
||||
// Assumption: only numbers in our versions.
|
||||
const firstParts = first.split('.').map((value) => parseInt(value));
|
||||
const secondParts = second.split('.').map((value) => parseInt(value));
|
||||
|
||||
for (let i = 0; i < Math.max(firstParts.length, secondParts.length); i++) {
|
||||
const first = i < firstParts.length ? firstParts[i] : 0;
|
||||
const second = i < secondParts.length ? secondParts[i] : 0;
|
||||
if (first < second) {
|
||||
return -1;
|
||||
}
|
||||
if (second < first) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (compareVersion(currentVersion, nextVersion) <= 0) {
|
||||
package.version = nextVersion;
|
||||
fs.writeFileSync(path + '/package.json', JSON.stringify(package, null, 2));
|
||||
}
|
||||
@@ -5,6 +5,6 @@ set -e
|
||||
for dir in ./npm/*; do
|
||||
pushd $dir > /dev/null
|
||||
echo Publishing `echo $dir | sed 's:.*/::'`
|
||||
npm publish --access public
|
||||
npm publish $@
|
||||
popd > /dev/null
|
||||
done
|
||||
|
||||
@@ -3,14 +3,17 @@ set -e
|
||||
|
||||
echo "Copying package.json to rive-react npm package folders"
|
||||
|
||||
cp package.json npm/react-canvas
|
||||
cp package.json npm/react-webgl
|
||||
|
||||
# Bump the version number of every npm module in the npm folder.
|
||||
for dir in ./npm/*; do
|
||||
echo $dir
|
||||
pushd $dir > /dev/null
|
||||
echo $dir
|
||||
if [ -f "./package.json" ]; then
|
||||
echo "Removing existing package.json..."
|
||||
rm "./package.json"
|
||||
echo "package.json deleted from $dir"
|
||||
fi
|
||||
cp ../../package.json ./
|
||||
repo_name=`echo $dir | sed 's:.*/::' | sed 's/_/-/g'`
|
||||
echo Setting package.json on npm packages
|
||||
echo $repo_name
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const fs = require('fs');
|
||||
const path = process.argv[2];
|
||||
const npmPackageSplit = process.argv[3].split('-');
|
||||
const renderer = npmPackageSplit[npmPackageSplit.length - 1];
|
||||
// extracts "webgl" or "canvas-lite" from the npm package name
|
||||
const renderer = npmPackageSplit.slice(1).join('-');
|
||||
const package = require(path + '/package.json');
|
||||
|
||||
function trimNpmPackage() {
|
||||
|
||||
@@ -2,14 +2,53 @@ import { Layout } from '@rive-app/canvas';
|
||||
import React, { ComponentProps } from 'react';
|
||||
import useRive from '../hooks/useRive';
|
||||
|
||||
export type RiveProps = {
|
||||
export interface RiveProps {
|
||||
/**
|
||||
* URL of the Rive asset, or path to where the public asset is stored.
|
||||
*/
|
||||
src: string;
|
||||
/**
|
||||
* Artboard to render from the Rive asset.
|
||||
* Defaults to the first artboard created.
|
||||
*/
|
||||
artboard?: string;
|
||||
/**
|
||||
* Specify a starting animation to play.
|
||||
*/
|
||||
animations?: string | string[];
|
||||
/**
|
||||
* Specify a starting state machine to play.
|
||||
*/
|
||||
stateMachines?: string | string[];
|
||||
/**
|
||||
* Specify a starting Layout object to set Fill and Alignment for the drawing surface. See docs at https://rive.app/community/doc/layout/docBl81zd1GB for more on layout configuration.
|
||||
*/
|
||||
layout?: Layout;
|
||||
/**
|
||||
* For `@rive-app/react-webgl`, sets this property to maintain a single WebGL context for multiple canvases. **We recommend to keep the default value** when rendering multiple Rive instances on a page.
|
||||
*/
|
||||
useOffscreenRenderer?: boolean;
|
||||
};
|
||||
/**
|
||||
* Specify whether to disable Rive listeners on the canvas, thus preventing any event listeners to be attached to the canvas element
|
||||
*/
|
||||
shouldDisableRiveListeners?: boolean;
|
||||
/**
|
||||
* Specify whether to resize the canvas to its container automatically
|
||||
*/
|
||||
shouldResizeCanvasToContainer?: boolean;
|
||||
/**
|
||||
* Enable Rive Events to be handled by the runtime. This means any special Rive Event may have
|
||||
* functionality that can be invoked implicitly when detected.
|
||||
*
|
||||
* For example, if during the render loop an OpenUrlEvent is detected, the
|
||||
* browser may try to open the specified URL in the payload.
|
||||
*
|
||||
* This flag is false by default to prevent any unwanted behaviors from taking place.
|
||||
* This means any special Rive Event will have to be handled manually by subscribing to
|
||||
* EventType.RiveEvent
|
||||
*/
|
||||
automaticallyHandleEvents?: boolean;
|
||||
}
|
||||
|
||||
const Rive = ({
|
||||
src,
|
||||
@@ -18,6 +57,10 @@ const Rive = ({
|
||||
stateMachines,
|
||||
layout,
|
||||
useOffscreenRenderer = true,
|
||||
shouldDisableRiveListeners = false,
|
||||
shouldResizeCanvasToContainer = true,
|
||||
automaticallyHandleEvents = false,
|
||||
children,
|
||||
...rest
|
||||
}: RiveProps & ComponentProps<'canvas'>) => {
|
||||
const params = {
|
||||
@@ -27,14 +70,17 @@ const Rive = ({
|
||||
layout,
|
||||
stateMachines,
|
||||
autoplay: true,
|
||||
shouldDisableRiveListeners,
|
||||
automaticallyHandleEvents,
|
||||
};
|
||||
|
||||
const options = {
|
||||
useOffscreenRenderer,
|
||||
shouldResizeCanvasToContainer,
|
||||
};
|
||||
|
||||
const { RiveComponent } = useRive(params, options);
|
||||
return <RiveComponent {...rest} />;
|
||||
return <RiveComponent {...rest}>{children}</RiveComponent>;
|
||||
};
|
||||
|
||||
export default Rive;
|
||||
|
||||
38
src/hooks/elementObserver.ts
Normal file
38
src/hooks/elementObserver.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
class FakeIntersectionObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
const MyIntersectionObserver =
|
||||
globalThis.IntersectionObserver || FakeIntersectionObserver;
|
||||
|
||||
class ElementObserver {
|
||||
private observer;
|
||||
|
||||
private elementsMap: Map<Element, Function> = new Map();
|
||||
|
||||
constructor() {
|
||||
this.observer = new MyIntersectionObserver(this.onObserved);
|
||||
}
|
||||
public onObserved = (entries: IntersectionObserverEntry[]) => {
|
||||
entries.forEach((entry) => {
|
||||
const elementCallback = this.elementsMap.get(entry.target as Element);
|
||||
if (elementCallback) {
|
||||
elementCallback(entry);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
public registerCallback(element: Element, callback: Function) {
|
||||
this.observer.observe(element);
|
||||
this.elementsMap.set(element, callback);
|
||||
}
|
||||
|
||||
public removeCallback(element: Element) {
|
||||
this.observer.unobserve(element);
|
||||
this.elementsMap.delete(element);
|
||||
}
|
||||
}
|
||||
|
||||
export default ElementObserver;
|
||||
94
src/hooks/useContainerSize.ts
Normal file
94
src/hooks/useContainerSize.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Dimensions } from '../types';
|
||||
|
||||
// There are polyfills for this, but they add hundreds of lines of code
|
||||
class FakeResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
function throttle(f: Function, delay: number) {
|
||||
let timer = 0;
|
||||
return function (this: Function, ...args: any) {
|
||||
clearTimeout(timer);
|
||||
timer = window.setTimeout(() => f.apply(this, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
const MyResizeObserver = globalThis.ResizeObserver || FakeResizeObserver;
|
||||
const hasResizeObserver = globalThis.ResizeObserver !== undefined;
|
||||
|
||||
const useResizeObserver = hasResizeObserver;
|
||||
const useWindowListener = !useResizeObserver;
|
||||
|
||||
/**
|
||||
* Hook to listen for a ref element's resize events being triggered. When resized,
|
||||
* it sets state to an object of {width: number, height: number} indicating the contentRect
|
||||
* size of the element at the new resize.
|
||||
*
|
||||
* @param containerRef - Ref element to listen for resize events on
|
||||
* @returns - Size object with width and height attributes
|
||||
*/
|
||||
export default function useSize(
|
||||
containerRef: React.MutableRefObject<HTMLElement | null>,
|
||||
shouldResizeCanvasToContainer = true
|
||||
) {
|
||||
const [size, setSize] = useState<Dimensions>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
|
||||
// internet explorer does not support ResizeObservers.
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined' && shouldResizeCanvasToContainer) {
|
||||
const handleResize = () => {
|
||||
setSize({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
};
|
||||
|
||||
if (useWindowListener) {
|
||||
// only pay attention to window size changes when we do not have the resizeObserver (IE only)
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
}
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}
|
||||
}, []);
|
||||
const observer = useRef(
|
||||
new MyResizeObserver(
|
||||
throttle((entries: any) => {
|
||||
if (useResizeObserver) {
|
||||
setSize({
|
||||
width: entries[entries.length - 1].contentRect.width,
|
||||
height: entries[entries.length - 1].contentRect.height,
|
||||
});
|
||||
}
|
||||
}, 0)
|
||||
)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const currentObserver = observer.current;
|
||||
if (!shouldResizeCanvasToContainer) {
|
||||
currentObserver.disconnect();
|
||||
return;
|
||||
}
|
||||
const containerEl = containerRef.current;
|
||||
if (containerRef.current && useResizeObserver) {
|
||||
currentObserver.observe(containerRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
currentObserver.disconnect();
|
||||
if (containerEl && useResizeObserver) {
|
||||
currentObserver.unobserve(containerEl);
|
||||
}
|
||||
};
|
||||
}, [containerRef, observer]);
|
||||
|
||||
return size;
|
||||
}
|
||||
51
src/hooks/useDevicePixelRatio.ts
Normal file
51
src/hooks/useDevicePixelRatio.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
/**
|
||||
* Listen for devicePixelRatio changes and set the new value accordingly. This could
|
||||
* happen for reasons such as:
|
||||
* - User moves window from retina screen display to a separate monitor
|
||||
* - User controls zoom settings on the browser
|
||||
*
|
||||
* Source: https://github.com/rexxars/use-device-pixel-ratio/blob/main/index.ts
|
||||
*
|
||||
* @param customDevicePixelRatio - Number to force a dpr to abide by, rather than using the window's
|
||||
*
|
||||
* @returns dpr: Number - Device pixel ratio; ratio of physical px to resolution in CSS pixels for current device
|
||||
*/
|
||||
export default function useDevicePixelRatio(customDevicePixelRatio?: number) {
|
||||
const dpr = customDevicePixelRatio || getDevicePixelRatio();
|
||||
const [currentDpr, setCurrentDpr] = useState(dpr);
|
||||
|
||||
useEffect(() => {
|
||||
const canListen = typeof window !== 'undefined' && 'matchMedia' in window;
|
||||
if (!canListen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateDpr = () => {
|
||||
const newDpr = customDevicePixelRatio || getDevicePixelRatio();
|
||||
setCurrentDpr(newDpr);
|
||||
};
|
||||
const mediaMatcher = window.matchMedia(
|
||||
`screen and (resolution: ${currentDpr}dppx)`
|
||||
);
|
||||
mediaMatcher.hasOwnProperty('addEventListener')
|
||||
? mediaMatcher.addEventListener('change', updateDpr)
|
||||
: mediaMatcher.addListener(updateDpr);
|
||||
|
||||
return () => {
|
||||
mediaMatcher.hasOwnProperty('removeEventListener')
|
||||
? mediaMatcher.removeEventListener('change', updateDpr)
|
||||
: mediaMatcher.removeListener(updateDpr);
|
||||
};
|
||||
}, [currentDpr, customDevicePixelRatio]);
|
||||
|
||||
return currentDpr;
|
||||
}
|
||||
|
||||
function getDevicePixelRatio(): number {
|
||||
const hasDprProp =
|
||||
typeof window !== 'undefined' &&
|
||||
typeof window.devicePixelRatio === 'number';
|
||||
const dpr = hasDprProp ? window.devicePixelRatio : 1;
|
||||
return Math.min(Math.max(1, dpr), 3);
|
||||
}
|
||||
32
src/hooks/useIntersectionObserver.ts
Normal file
32
src/hooks/useIntersectionObserver.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useCallback } from 'react';
|
||||
import ElementObserver from './elementObserver';
|
||||
|
||||
let observer: ElementObserver;
|
||||
const getObserver = () => {
|
||||
if(!observer) {
|
||||
observer = new ElementObserver();
|
||||
}
|
||||
return observer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to observe elements when they are intersecting with the viewport
|
||||
*
|
||||
* @returns - API to observer and unobserve elements
|
||||
*/
|
||||
export default function useIntersectionObserver() {
|
||||
const observe = useCallback((element: Element, callback: Function) => {
|
||||
const observer = getObserver();
|
||||
observer.registerCallback(element, callback);
|
||||
}, []);
|
||||
|
||||
const unobserve = useCallback((element: Element) => {
|
||||
const observer = getObserver();
|
||||
observer.removeCallback(element);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
observe,
|
||||
unobserve,
|
||||
};
|
||||
}
|
||||
195
src/hooks/useResizeCanvas.ts
Normal file
195
src/hooks/useResizeCanvas.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { useEffect, useState, MutableRefObject, useCallback } from 'react';
|
||||
import { Bounds } from '@rive-app/canvas';
|
||||
import { Dimensions, UseRiveOptions } from '../types';
|
||||
import useDevicePixelRatio from './useDevicePixelRatio';
|
||||
import useContainerSize from './useContainerSize';
|
||||
import { getOptions } from '../utils';
|
||||
|
||||
interface UseResizeCanvasProps {
|
||||
/**
|
||||
* Whether or not Rive is loaded and renderer is associated with the canvas
|
||||
*/
|
||||
riveLoaded: boolean;
|
||||
/**
|
||||
* Ref to the canvas element
|
||||
*/
|
||||
canvasElem: HTMLCanvasElement | null;
|
||||
/**
|
||||
* Ref to the container element of the canvas
|
||||
*/
|
||||
containerRef: MutableRefObject<HTMLElement | null>;
|
||||
/**
|
||||
* (Optional) Callback to be invoked after the canvas has been resized due to a resize
|
||||
* of its parent container. This is where you would want to reset the layout
|
||||
* dimensions for the Rive renderer to dictate the new min/max bounds of the
|
||||
* canvas.
|
||||
*
|
||||
* Using the high-level JS runtime, this might be a simple call to `rive.resizeToCanvas()`
|
||||
* Using the low-level JSruntime, this might be invoking the renderer's `.align()` method
|
||||
* with the Layout and min/max X/Y values of the canvas.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
onCanvasHasResized?: () => void;
|
||||
/**
|
||||
* (Optional) Options passed to the useRive hook, including the shouldResizeCanvasToContainer option
|
||||
* which prevents the canvas element from resizing to its parent container
|
||||
*/
|
||||
options?: Partial<UseRiveOptions>;
|
||||
/**
|
||||
* (Optional) AABB bounds of the artboard. If provided, the canvas will be sized to the artboard
|
||||
* height if the fitCanvasToArtboardHeight option is true.
|
||||
*/
|
||||
artboardBounds?: Bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper hook to listen for changes in the <canvas> parent container size and size the <canvas>
|
||||
* to match. If a resize event has occurred, a supplied callback (onCanvasHasResized)
|
||||
* will be inokved to allow for any re-calculation needed (i.e. Rive layout on the canvas).
|
||||
*
|
||||
* This hook is useful if you are not intending to use the `useRive` hook yourself, but still
|
||||
* want to use the auto-sizing logic on the canvas/container.
|
||||
*
|
||||
* @param props - Object to supply necessary props to the hook
|
||||
*/
|
||||
export default function useResizeCanvas({
|
||||
riveLoaded = false,
|
||||
canvasElem,
|
||||
containerRef,
|
||||
options = {},
|
||||
onCanvasHasResized,
|
||||
artboardBounds,
|
||||
}: UseResizeCanvasProps) {
|
||||
const presetOptions = getOptions(options);
|
||||
const [
|
||||
{ height: lastContainerHeight, width: lastContainerWidth },
|
||||
setLastContainerDimensions,
|
||||
] = useState<Dimensions>({
|
||||
height: 0,
|
||||
width: 0,
|
||||
});
|
||||
const [
|
||||
{ height: lastCanvasHeight, width: lastCanvasWidth },
|
||||
setLastCanvasSize,
|
||||
] = useState<Dimensions>({
|
||||
height: 0,
|
||||
width: 0,
|
||||
});
|
||||
|
||||
const [isFirstSizing, setIsFirstSizing] = useState(true);
|
||||
|
||||
const {
|
||||
fitCanvasToArtboardHeight,
|
||||
shouldResizeCanvasToContainer,
|
||||
useDevicePixelRatio: shouldUseDevicePixelRatio,
|
||||
customDevicePixelRatio,
|
||||
} = presetOptions;
|
||||
|
||||
const containerSize = useContainerSize(
|
||||
containerRef,
|
||||
shouldResizeCanvasToContainer
|
||||
);
|
||||
const currentDevicePixelRatio = useDevicePixelRatio(customDevicePixelRatio);
|
||||
|
||||
const { maxX, maxY } = artboardBounds ?? {};
|
||||
|
||||
const getContainerDimensions = useCallback(() => {
|
||||
const width = containerRef.current?.clientWidth ?? 0;
|
||||
const height = containerRef.current?.clientHeight ?? 0;
|
||||
if (fitCanvasToArtboardHeight && artboardBounds) {
|
||||
const { maxY, maxX } = artboardBounds;
|
||||
return { width, height: width * (maxY / maxX) };
|
||||
}
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}, [containerRef, fitCanvasToArtboardHeight, maxX, maxY]);
|
||||
|
||||
useEffect(() => {
|
||||
// If Rive is not ready, the container is not ready, or the user supplies a flag
|
||||
// to not resize the canvas to the container, then return early
|
||||
if (
|
||||
!shouldResizeCanvasToContainer ||
|
||||
!containerRef.current ||
|
||||
!riveLoaded
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width, height } = getContainerDimensions();
|
||||
let hasResized = false;
|
||||
if (canvasElem) {
|
||||
// Check if the canvas parent container bounds have changed and set
|
||||
// new values accordingly
|
||||
const boundsChanged =
|
||||
width !== lastContainerWidth || height !== lastContainerHeight;
|
||||
if (presetOptions.fitCanvasToArtboardHeight && boundsChanged) {
|
||||
containerRef.current.style.height = height + 'px';
|
||||
hasResized = true;
|
||||
}
|
||||
if (presetOptions.useDevicePixelRatio) {
|
||||
// Check if devicePixelRatio may have changed and get new canvas
|
||||
// width/height values to set the size
|
||||
const canvasSizeChanged =
|
||||
width * currentDevicePixelRatio !== lastCanvasWidth ||
|
||||
height * currentDevicePixelRatio !== lastCanvasHeight;
|
||||
if (boundsChanged || canvasSizeChanged) {
|
||||
const newCanvasWidthProp = currentDevicePixelRatio * width;
|
||||
const newCanvasHeightProp = currentDevicePixelRatio * height;
|
||||
canvasElem.width = newCanvasWidthProp;
|
||||
canvasElem.height = newCanvasHeightProp;
|
||||
canvasElem.style.width = width + 'px';
|
||||
canvasElem.style.height = height + 'px';
|
||||
setLastCanvasSize({
|
||||
width: newCanvasWidthProp,
|
||||
height: newCanvasHeightProp,
|
||||
});
|
||||
hasResized = true;
|
||||
}
|
||||
} else if (boundsChanged) {
|
||||
canvasElem.width = width;
|
||||
canvasElem.height = height;
|
||||
setLastCanvasSize({
|
||||
width: width,
|
||||
height: height,
|
||||
});
|
||||
hasResized = true;
|
||||
}
|
||||
setLastContainerDimensions({ width, height });
|
||||
}
|
||||
|
||||
// Callback to perform any Rive-related actions after resizing the canvas
|
||||
// (i.e., reset the Rive layout in the render loop)
|
||||
if (onCanvasHasResized && (isFirstSizing || hasResized)) {
|
||||
onCanvasHasResized && onCanvasHasResized();
|
||||
}
|
||||
isFirstSizing && setIsFirstSizing(false);
|
||||
}, [
|
||||
canvasElem,
|
||||
containerRef,
|
||||
containerSize,
|
||||
currentDevicePixelRatio,
|
||||
getContainerDimensions,
|
||||
isFirstSizing,
|
||||
setIsFirstSizing,
|
||||
lastCanvasHeight,
|
||||
lastCanvasWidth,
|
||||
lastContainerHeight,
|
||||
lastContainerWidth,
|
||||
onCanvasHasResized,
|
||||
shouldResizeCanvasToContainer,
|
||||
fitCanvasToArtboardHeight,
|
||||
shouldUseDevicePixelRatio,
|
||||
riveLoaded,
|
||||
]);
|
||||
|
||||
// Reset width and height values when the canvas changes
|
||||
useEffect(() => {
|
||||
setLastCanvasSize({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
}, [canvasElem]);
|
||||
}
|
||||
@@ -6,14 +6,12 @@ import React, {
|
||||
ComponentProps,
|
||||
RefCallback,
|
||||
} from 'react';
|
||||
import { Rive, EventType } from '@rive-app/canvas';
|
||||
import {
|
||||
UseRiveParameters,
|
||||
UseRiveOptions,
|
||||
RiveState,
|
||||
Dimensions,
|
||||
} from '../types';
|
||||
import { useWindowSize } from '../utils';
|
||||
import { Rive, EventType, Fit } from '@rive-app/canvas';
|
||||
import { UseRiveParameters, UseRiveOptions, RiveState } from '../types';
|
||||
import useResizeCanvas from './useResizeCanvas';
|
||||
import useDevicePixelRatio from './useDevicePixelRatio';
|
||||
import { getOptions } from '../utils';
|
||||
import useIntersectionObserver from './useIntersectionObserver';
|
||||
|
||||
type RiveComponentProps = {
|
||||
setContainerRef: RefCallback<HTMLElement>;
|
||||
@@ -25,6 +23,7 @@ function RiveComponent({
|
||||
setCanvasRef,
|
||||
className = '',
|
||||
style,
|
||||
children,
|
||||
...rest
|
||||
}: RiveComponentProps & ComponentProps<'canvas'>) {
|
||||
const containerStyle = {
|
||||
@@ -39,27 +38,17 @@ function RiveComponent({
|
||||
className={className}
|
||||
{...(!className && { style: containerStyle })}
|
||||
>
|
||||
<canvas ref={setCanvasRef} style={{ verticalAlign: 'top' }} {...rest} />
|
||||
<canvas
|
||||
ref={setCanvasRef}
|
||||
style={{ verticalAlign: 'top', width: 0, height: 0 }}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</canvas>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
useDevicePixelRatio: true,
|
||||
fitCanvasToArtboardHeight: false,
|
||||
useOffscreenRenderer: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns options, with defaults set.
|
||||
*
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
function getOptions(opts: Partial<UseRiveOptions>) {
|
||||
return Object.assign({}, defaultOptions, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Hook for loading a Rive file.
|
||||
*
|
||||
@@ -78,113 +67,92 @@ export default function useRive(
|
||||
riveParams?: UseRiveParameters,
|
||||
opts: Partial<UseRiveOptions> = {}
|
||||
): RiveState {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const [canvasElem, setCanvasElem] = useState<HTMLCanvasElement | null>(null);
|
||||
const containerRef = useRef<HTMLElement | null>(null);
|
||||
const [rive, setRive] = useState<Rive | null>(null);
|
||||
const [dimensions, setDimensions] = useState<Dimensions>({
|
||||
height: 0,
|
||||
width: 0,
|
||||
});
|
||||
|
||||
// Listen to changes in the window sizes and update the bounds when changes
|
||||
// occur.
|
||||
const windowSize = useWindowSize();
|
||||
const [rive, setRive] = useState<Rive | null>(null);
|
||||
|
||||
const isParamsLoaded = Boolean(riveParams);
|
||||
const options = getOptions(opts);
|
||||
|
||||
/**
|
||||
* Gets the intended dimensions of the canvas element.
|
||||
*
|
||||
* The intended dimensions are those of the container element, unless the
|
||||
* option `fitCanvasToArtboardHeight` is true, then they are adjusted to
|
||||
* the height of the artboard.
|
||||
*
|
||||
* @returns Dimensions object.
|
||||
*/
|
||||
function getCanvasDimensions() {
|
||||
const { width, height } =
|
||||
containerRef.current?.getBoundingClientRect() ?? new DOMRect(0, 0, 0, 0);
|
||||
|
||||
if (rive && options.fitCanvasToArtboardHeight) {
|
||||
const { maxY, maxX } = rive.bounds;
|
||||
return { width, height: width * (maxY / maxX) };
|
||||
}
|
||||
return { width, height };
|
||||
}
|
||||
const devicePixelRatio = useDevicePixelRatio();
|
||||
|
||||
/**
|
||||
* Updates the width and height of the canvas.
|
||||
* When the canvas/parent container resize, reset the Rive layout to match the
|
||||
* new (0, 0, canvas.width, canvas.height) bounds in the render loop
|
||||
*/
|
||||
function updateBounds() {
|
||||
if (!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width, height } = getCanvasDimensions();
|
||||
const boundsChanged =
|
||||
width !== dimensions.width || height !== dimensions.height;
|
||||
if (canvasRef.current && rive && boundsChanged) {
|
||||
if (options.fitCanvasToArtboardHeight) {
|
||||
containerRef.current.style.height = height + 'px';
|
||||
}
|
||||
if (options.useDevicePixelRatio) {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvasRef.current.width = dpr * width;
|
||||
canvasRef.current.height = dpr * height;
|
||||
canvasRef.current.style.width = width + 'px';
|
||||
canvasRef.current.style.height = height + 'px';
|
||||
} else {
|
||||
canvasRef.current.width = width;
|
||||
canvasRef.current.height = height;
|
||||
}
|
||||
setDimensions({ width, height });
|
||||
|
||||
// Updating the canvas width or height will clear the canvas, so call
|
||||
// startRendering() to redraw the current frame as the animation might
|
||||
// be paused and not advancing.
|
||||
rive.startRendering();
|
||||
}
|
||||
|
||||
// Always resize to Canvas
|
||||
const onCanvasHasResized = useCallback(() => {
|
||||
if (rive) {
|
||||
if (rive.layout && rive.layout.fit === Fit.Layout) {
|
||||
if (canvasElem) {
|
||||
const resizeFactor = devicePixelRatio * rive.layout.layoutScaleFactor;
|
||||
rive.devicePixelRatioUsed = devicePixelRatio;
|
||||
rive.artboardWidth = canvasElem?.width / resizeFactor;
|
||||
rive.artboardHeight = canvasElem?.height / resizeFactor;
|
||||
}
|
||||
}
|
||||
|
||||
rive.startRendering();
|
||||
rive.resizeToCanvas();
|
||||
}
|
||||
}
|
||||
}, [rive, devicePixelRatio]);
|
||||
|
||||
/**
|
||||
* Listen to changes on the windowSize and the rive file being loaded
|
||||
* and update the canvas bounds as needed.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (rive) {
|
||||
updateBounds();
|
||||
}
|
||||
}, [rive, windowSize]);
|
||||
// Watch the canvas parent container resize and size the canvas to match
|
||||
useResizeCanvas({
|
||||
riveLoaded: !!rive,
|
||||
canvasElem,
|
||||
containerRef,
|
||||
options,
|
||||
onCanvasHasResized,
|
||||
artboardBounds: rive?.bounds,
|
||||
});
|
||||
|
||||
/**
|
||||
* Ref callback called when the canvas element mounts and unmounts.
|
||||
*/
|
||||
const setCanvasRef: RefCallback<HTMLCanvasElement> = useCallback(
|
||||
(canvas: HTMLCanvasElement | null) => {
|
||||
if (canvas && riveParams) {
|
||||
const { useOffscreenRenderer } = options;
|
||||
const r = new Rive({
|
||||
useOffscreenRenderer,
|
||||
...riveParams,
|
||||
canvas,
|
||||
});
|
||||
r.on(EventType.Load, () => setRive(r));
|
||||
} else if (canvas === null && canvasRef.current) {
|
||||
canvasRef.current.height = 0;
|
||||
canvasRef.current.width = 0;
|
||||
if (canvas === null && canvasElem) {
|
||||
canvasElem.height = 0;
|
||||
canvasElem.width = 0;
|
||||
}
|
||||
|
||||
canvasRef.current = canvas;
|
||||
setCanvasElem(canvas);
|
||||
},
|
||||
[isParamsLoaded]
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasElem || !riveParams) {
|
||||
return;
|
||||
}
|
||||
let isLoaded = rive != null;
|
||||
let r: Rive | null;
|
||||
if (rive == null) {
|
||||
const { useOffscreenRenderer } = options;
|
||||
r = new Rive({
|
||||
useOffscreenRenderer,
|
||||
...riveParams,
|
||||
canvas: canvasElem,
|
||||
});
|
||||
r.on(EventType.Load, () => {
|
||||
isLoaded = true;
|
||||
// Check if the component/canvas is mounted before setting state to avoid setState
|
||||
// on an unmounted component in some rare cases
|
||||
if (canvasElem) {
|
||||
setRive(r);
|
||||
} else {
|
||||
// If unmounted, cleanup the rive object immediately
|
||||
r!.cleanup();
|
||||
}
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
if (!isLoaded) {
|
||||
r?.cleanup();
|
||||
}
|
||||
};
|
||||
}, [canvasElem, isParamsLoaded, rive]);
|
||||
/**
|
||||
* Ref callback called when the container element mounts
|
||||
*/
|
||||
@@ -199,33 +167,75 @@ export default function useRive(
|
||||
* Set up IntersectionObserver to stop rendering if the animation is not in
|
||||
* view.
|
||||
*/
|
||||
const { observe, unobserve } = useIntersectionObserver();
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(([entry]) => {
|
||||
let timeoutId: ReturnType<typeof setTimeout>;
|
||||
let isPaused = false;
|
||||
// This is a workaround to retest whether an element is offscreen or not.
|
||||
// There seems to be a bug in Chrome that triggers an intersection change when an element
|
||||
// is moved within the DOM using insertBefore.
|
||||
// For some reason, when this is called whithin the context of a React application, the
|
||||
// intersection callback is called only once reporting isIntersecting as false but never
|
||||
// triggered back with isIntersecting as true.
|
||||
// For this reason we retest after 10 millisecond whether the element is actually off the
|
||||
// viewport or not.
|
||||
const retestIntersection = () => {
|
||||
if (canvasElem && isPaused) {
|
||||
const size = canvasElem.getBoundingClientRect();
|
||||
const isIntersecting =
|
||||
size.width > 0 &&
|
||||
size.height > 0 &&
|
||||
size.top <
|
||||
(window.innerHeight || document.documentElement.clientHeight) &&
|
||||
size.bottom > 0 &&
|
||||
size.left <
|
||||
(window.innerWidth || document.documentElement.clientWidth) &&
|
||||
size.right > 0;
|
||||
if (isIntersecting) {
|
||||
rive?.startRendering();
|
||||
isPaused = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
const onChange = (entry: IntersectionObserverEntry) => {
|
||||
entry.isIntersecting
|
||||
? rive && rive.startRendering()
|
||||
: rive && rive.stopRendering();
|
||||
});
|
||||
|
||||
if (canvasRef.current) {
|
||||
observer.observe(canvasRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
isPaused = !entry.isIntersecting;
|
||||
clearTimeout(timeoutId);
|
||||
if (!entry.isIntersecting && entry.boundingClientRect.width === 0) {
|
||||
timeoutId = setTimeout(retestIntersection, 10);
|
||||
}
|
||||
};
|
||||
}, [rive]);
|
||||
if (canvasElem && options.shouldUseIntersectionObserver !== false) {
|
||||
observe(canvasElem, onChange);
|
||||
}
|
||||
return () => {
|
||||
if (canvasElem) {
|
||||
unobserve(canvasElem);
|
||||
}
|
||||
};
|
||||
}, [
|
||||
observe,
|
||||
unobserve,
|
||||
rive,
|
||||
canvasElem,
|
||||
options.shouldUseIntersectionObserver,
|
||||
]);
|
||||
|
||||
/**
|
||||
* On unmount, stop rive from rendering.
|
||||
* On unmount, call cleanup to cleanup any WASM generated objects that need
|
||||
* to be manually destroyed.
|
||||
*/
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (rive) {
|
||||
rive.stop();
|
||||
rive.cleanup();
|
||||
setRive(null);
|
||||
}
|
||||
};
|
||||
}, [rive]);
|
||||
}, [rive, canvasElem]);
|
||||
|
||||
/**
|
||||
* Listen for changes in the animations params
|
||||
@@ -253,11 +263,12 @@ export default function useRive(
|
||||
/>
|
||||
);
|
||||
},
|
||||
[]
|
||||
[setCanvasRef, setContainerRef]
|
||||
);
|
||||
|
||||
return {
|
||||
canvas: canvasRef.current,
|
||||
canvas: canvasElem,
|
||||
container: containerRef.current,
|
||||
setCanvasRef,
|
||||
setContainerRef,
|
||||
rive,
|
||||
|
||||
57
src/hooks/useRiveFile.ts
Normal file
57
src/hooks/useRiveFile.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import type {
|
||||
UseRiveFileParameters,
|
||||
RiveFileState,
|
||||
FileStatus,
|
||||
} from '../types';
|
||||
import { EventType, RiveFile } from '@rive-app/canvas';
|
||||
|
||||
/**
|
||||
* Custom hook for initializing and managing a RiveFile instance within a component.
|
||||
* It sets up a RiveFile based on provided source parameters (URL or ArrayBuffer) and ensures
|
||||
* proper cleanup to avoid memory leaks when the component unmounts or inputs change.
|
||||
*
|
||||
* @param params - Object containing parameters accepted by the Rive file in the @rive-app/canvas runtime,
|
||||
*
|
||||
* @returns {RiveFileState} Contains the active RiveFile instance (`riveFile`) and the loading status.
|
||||
*/
|
||||
function useRiveFile(params: UseRiveFileParameters): RiveFileState {
|
||||
const [riveFile, setRiveFile] = useState<RiveFile | null>(null);
|
||||
const [status, setStatus] = useState<FileStatus>('idle');
|
||||
|
||||
useEffect(() => {
|
||||
let file: RiveFile | null = null;
|
||||
|
||||
const loadRiveFile = async () => {
|
||||
try {
|
||||
setStatus('loading');
|
||||
file = new RiveFile(params);
|
||||
file.init();
|
||||
file.on(EventType.Load, () => {
|
||||
// We request an instance to add +1 to the referencesCount so it doesn't get destroyed
|
||||
// while this hook is active
|
||||
file?.getInstance();
|
||||
setRiveFile(file);
|
||||
setStatus('success');
|
||||
});
|
||||
file.on(EventType.LoadError, () => {
|
||||
setStatus('failed');
|
||||
});
|
||||
setRiveFile(file);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setStatus('failed');
|
||||
}
|
||||
};
|
||||
|
||||
loadRiveFile();
|
||||
|
||||
return () => {
|
||||
file?.cleanup();
|
||||
};
|
||||
}, [params.src, params.buffer]);
|
||||
|
||||
return { riveFile, status };
|
||||
}
|
||||
|
||||
export default useRiveFile;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Rive, StateMachineInput } from '@rive-app/canvas';
|
||||
import { EventType, Rive, StateMachineInput } from '@rive-app/canvas';
|
||||
|
||||
/**
|
||||
* Custom hook for fetching a stateMachine input from a rive file.
|
||||
@@ -18,21 +18,33 @@ export default function useStateMachineInput(
|
||||
const [input, setInput] = useState<StateMachineInput | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rive || !stateMachineName || !inputName) {
|
||||
setInput(null);
|
||||
}
|
||||
|
||||
if (rive && stateMachineName && inputName) {
|
||||
const inputs = rive.stateMachineInputs(stateMachineName);
|
||||
if (inputs) {
|
||||
const selectedInput = inputs.find((input) => input.name === inputName);
|
||||
if (initialValue !== undefined && selectedInput) {
|
||||
selectedInput.value = initialValue;
|
||||
}
|
||||
setInput(selectedInput || null);
|
||||
function setStateMachineInput() {
|
||||
if (!rive || !stateMachineName || !inputName) {
|
||||
setInput(null);
|
||||
}
|
||||
} else {
|
||||
setInput(null);
|
||||
|
||||
if (rive && stateMachineName && inputName) {
|
||||
const inputs = rive.stateMachineInputs(stateMachineName);
|
||||
if (inputs) {
|
||||
const selectedInput = inputs.find(
|
||||
(input) => input.name === inputName
|
||||
);
|
||||
if (initialValue !== undefined && selectedInput) {
|
||||
selectedInput.value = initialValue;
|
||||
}
|
||||
setInput(selectedInput || null);
|
||||
}
|
||||
} else {
|
||||
setInput(null);
|
||||
}
|
||||
}
|
||||
setStateMachineInput();
|
||||
if (rive) {
|
||||
rive.on(EventType.Load, () => {
|
||||
// Check if the component/canvas is mounted before setting state to avoid setState
|
||||
// on an unmounted component in some rare cases
|
||||
setStateMachineInput();
|
||||
});
|
||||
}
|
||||
}, [rive]);
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Rive from './components/Rive';
|
||||
import Rive, { RiveProps } from './components/Rive';
|
||||
import useRive from './hooks/useRive';
|
||||
import useStateMachineInput from './hooks/useStateMachineInput';
|
||||
import useResizeCanvas from './hooks/useResizeCanvas';
|
||||
import useRiveFile from './hooks/useRiveFile';
|
||||
|
||||
export default Rive;
|
||||
export { useRive, useStateMachineInput };
|
||||
export { RiveState, UseRiveParameters, UseRiveOptions } from './types';
|
||||
export { useRive, useStateMachineInput, useResizeCanvas, useRiveFile , RiveProps };
|
||||
export { RiveState, UseRiveParameters, UseRiveFileParameters, UseRiveOptions } from './types';
|
||||
export * from '@rive-app/canvas';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user