Compare commits

..

1 Commits

Author SHA1 Message Date
Arthur Vivian
7c14d23da8 Add example of webgl issues 2021-12-15 17:41:28 +00:00
129 changed files with 20698 additions and 17709 deletions

View File

@@ -3,11 +3,7 @@ module.exports = {
browser: true,
es2021: true,
},
extends: [
'plugin:react/recommended',
'prettier',
'plugin:storybook/recommended',
],
extends: ['plugin:react/recommended', 'prettier'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
@@ -16,23 +12,11 @@ module.exports = {
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['@typescript-eslint', 'prettier', 'react-hooks'],
plugins: ['@typescript-eslint', 'prettier'],
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: {
version: 'detect',
},
},
};

View File

@@ -1,26 +0,0 @@
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"]
})

View File

@@ -1,58 +1,35 @@
name: Publish to NPM
on:
workflow_dispatch:
inputs:
major:
description: 'Major'
type: boolean
default: false
minor:
description: 'Minor'
type: boolean
default: false
pull_request:
types: [closed]
branches:
- main
jobs:
publish_job:
merge_job:
runs-on: ubuntu-latest
steps:
- 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 }}
- uses: actions/setup-node@v2
with:
node-version: '16.x'
node-version: '12.x'
registry-url: 'https://registry.npmjs.org'
- name: Install Modules
run: npm install
run: npm ci
- name: Run type check
run: npm run types:check
- name: Run Linter
run: npm run lint
- name: Run Tests
run: npm test
- if: ${{ inputs.major == true }}
name: Major Release - Bump version number, update changelog, push and tag
run: npm run release:major
- name: Build
run: npm run build
- 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
env:
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
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 }}
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 }}
run: npm run release

View File

@@ -7,7 +7,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Install Modules
run: npm install
run: npm ci
- name: Run type check
run: npm run types:check
- name: Run Linter

1
.gitignore vendored
View File

@@ -5,4 +5,3 @@ dist
.idea
.vscode
examples/**/package-lock.json
package-lock.json

View File

@@ -1,7 +1,5 @@
{
"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"
@@ -14,12 +12,6 @@
"releaseName": "${version}"
},
"hooks": {
"after:version:bump": [
"npm run build",
"npm run setup-builds",
"npm run setup-packages",
"npx auto-changelog -p",
"npm run publish:all"
]
"after:bump": ["npx auto-changelog -p", "git add ./CHANGELOG.md"]
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +0,0 @@
# 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.

180
README.md
View File

@@ -4,73 +4,151 @@
# Rive React
![Rive hero image](https://cdn.rive.app/rive_logo_dark_bg.png)
React Runtime for [Rive](https://rive.app).
A React runtime library 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.
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.
Detailed runtime documentation can be found in [Rive's help center](https://help.rive.app/runtimes).
## Table of contents
## Create and ship interactive animations to any platform
- :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)
[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.
## Rive overview
## Installation
[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.
```
npm i --save rive-react
```
:house_with_garden: [Homepage](https://rive.app/)
_Note: This library is using React hooks so the minimum version required for both react and react-dom is 16.8.0._
:blue_book: [General help docs](https://rive.app/community/doc/)
## Usage
🛠 [Rive Forums](https://rive.app/community/forums/home)
### Component
## Getting started
Rive React provides a basic component as it's default import for displaying simple animations.
Follow along with the link below for a quick start in getting Rive React integrated into your React apps.
```js
import Rive from 'rive-react';
- [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)
function Example() {
return <Rive src="loader.riv" />;
}
For more information, see the Runtime sections of the Rive help documentation:
export default Example;
```
- [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)
#### Props
## Supported versions
- `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.
- `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 `div` element can also be passed to the `Rive` component and used in the same manner._
This library supports React versions `^16.8.0` through `^18.0.0`.
### 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 `div` 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`.
### 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.
#### Return Value
A Rive.js `stateMachineInput` object.
## Examples
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.
- [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)
### Awesome Rive
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.
## Migration guides
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!
[Migration guides](https://rive.app/community/doc/migrating-from-v3-to-v4/dociIPXVHKFF)
## Contributing
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.
## Issues
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.
The [examples](examples) shows a number of different ways to use Rive React. See the instructions for each example to run locally.

View File

@@ -1,74 +0,0 @@
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;

View File

@@ -1,16 +0,0 @@
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;

View File

@@ -1,7 +0,0 @@
html, body {
height: 100%;
}
#storybook-root {
height: 100%;
}

View File

@@ -21,5 +21,3 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*storybook.log

View File

@@ -0,0 +1,14 @@
# 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
```

View File

@@ -0,0 +1,44 @@
{
"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"
]
}
}

View File

@@ -0,0 +1,17 @@
<!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>

View File

Binary file not shown.

View File

@@ -0,0 +1,19 @@
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;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -6,12 +6,6 @@
"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,

View File

@@ -0,0 +1,14 @@
# 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
```

View File

@@ -0,0 +1,36 @@
{
"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": "^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"
]
}
}

View File

@@ -0,0 +1,17 @@
<!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>

View File

Binary file not shown.

View File

@@ -0,0 +1,19 @@
import { useRive } from 'rive-react';
function App() {
const params = {
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;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

14
examples/basic/README.md Normal file
View File

@@ -0,0 +1,14 @@
# 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
```

View File

@@ -0,0 +1,36 @@
{
"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"
]
}
}

View File

Binary file not shown.

View File

@@ -0,0 +1,17 @@
<!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>

View File

Binary file not shown.

14
examples/basic/src/App.js Normal file
View File

@@ -0,0 +1,14 @@
import SizeExample from './SizeExample';
import FlowerExample from './FlowerExample';
function App() {
return (
// The animation will fit to the parent element.
<>
<SizeExample />
<FlowerExample />
</>
);
}
export default App;

View File

@@ -0,0 +1,48 @@
import { useEffect, useState } from 'react';
import { useRive } from 'rive-react';
function FlowerExample() {
const { rive, canvas, RiveComponent } = useRive({
src: 'flower.riv',
artboard: 'Motion',
animations: 'Animation 1',
autoplay: true,
});
useEffect(() => {
console.log('change');
if (canvas) {
canvas.width = 200;
canvas.height = 200;
}
}, [canvas]);
function onLargerClick() {
if (rive && canvas) {
canvas.width = canvas.width + 50;
canvas.height = canvas.height + 50;
rive.resizeToCanvas();
}
}
function onSmallerClick() {
if (rive && canvas && canvas.width > 0) {
canvas.width = canvas.width - 50;
canvas.height = canvas.height - 50;
rive.resizeToCanvas();
}
}
return (
// The animation will fit to the parent element.
<>
<button onClick={onLargerClick}>Larger</button>
<button onClick={onSmallerClick}>Smaller</button>
<div>
<RiveComponent />
</div>
</>
);
}
export default FlowerExample;

View File

@@ -0,0 +1,48 @@
import { useEffect, useState } from 'react';
import { useRive } from 'rive-react';
function SizeExample() {
const { rive, canvas, RiveComponent } = useRive({
src: 'poison-loader.riv',
// artboard: 'Motion',
// animations: 'Animation 1',
autoplay: true,
});
useEffect(() => {
console.log('change');
if (canvas) {
canvas.width = 200;
canvas.height = 200;
}
}, [canvas]);
function onLargerClick() {
if (rive && canvas) {
canvas.width = canvas.width + 50;
canvas.height = canvas.height + 50;
rive.resizeToCanvas();
}
}
function onSmallerClick() {
if (rive && canvas && canvas.width > 0) {
canvas.width = canvas.width - 50;
canvas.height = canvas.height - 50;
rive.resizeToCanvas();
}
}
return (
// The animation will fit to the parent element.
<>
<button onClick={onLargerClick}>Larger</button>
<button onClick={onSmallerClick}>Smaller</button>
<div>
<RiveComponent />
</div>
</>
);
}
export default SizeExample;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -1,56 +0,0 @@
{
"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",
"test-storybook": "test-storybook"
},
"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",
"@storybook/test-runner": "^0.22.0",
"eslint-plugin-storybook": "^0.12.0",
"storybook": "^8.6.12",
"webpack": "^5.99.6"
}
}

View File

@@ -0,0 +1,14 @@
# 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
```

View File

@@ -0,0 +1,36 @@
{
"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"
]
}
}

View File

@@ -0,0 +1,17 @@
<!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>

View File

Binary file not shown.

View File

@@ -0,0 +1,32 @@
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;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -0,0 +1,14 @@
# 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
```

View File

@@ -0,0 +1,36 @@
{
"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"
]
}
}

View File

@@ -0,0 +1,17 @@
<!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>

View File

Binary file not shown.

View File

@@ -0,0 +1,50 @@
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;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -1,17 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react';
import DataBinding from './DataBinding';
const meta = {
title: 'DataBinding',
component: DataBinding,
parameters: {
layout: 'fullscreen',
},
args: {},
} satisfies Meta<typeof DataBinding>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};

View File

@@ -1,147 +0,0 @@
import React, { useEffect } from 'react';
import {
useRive,
useViewModel,
useViewModelInstance,
useViewModelInstanceColor,
useViewModelInstanceNumber,
useViewModelInstanceString,
useViewModelInstanceEnum,
useViewModelInstanceTrigger,
} from '@rive-app/react-webgl2';
const randomValue = () => Math.random() * 200 - 100;
const DataBinding = () => {
const { rive, RiveComponent } = useRive({
src: 'stocks.riv',
artboard: 'Main',
stateMachines: 'State Machine 1',
autoplay: true,
autoBind: false,
});
// Get the default instance of the view model
const viewModel = useViewModel(rive, { name: 'Dashboard' });
const viewModelInstance = useViewModelInstance(viewModel, { rive });
// Get the view model instance properties
const { setValue: setTitle } = useViewModelInstanceString(
'title',
viewModelInstance
);
const { setValue: setLogoShape } = useViewModelInstanceEnum(
'logoShape',
viewModelInstance
);
const { setValue: setRootColor } = useViewModelInstanceColor(
'rootColor',
viewModelInstance
);
const { trigger: triggerSpinLogo } = useViewModelInstanceTrigger(
'triggerSpinLogo',
viewModelInstance
);
useViewModelInstanceTrigger('triggerButton', viewModelInstance, {
onTrigger: () => console.log('Button Triggered!'),
});
// Apple Values
const { setValue: setAppleName } = useViewModelInstanceString(
'apple/name',
viewModelInstance
);
const { setValue: setAppleStockChange } = useViewModelInstanceNumber(
'apple/stockChange',
viewModelInstance
);
const { value: appleColor } = useViewModelInstanceColor(
'apple/currentColor',
viewModelInstance
);
// Apple Values
const { setValue: setMicrosoftName } = useViewModelInstanceString(
'microsoft/name',
viewModelInstance
);
const { setValue: setMicrosoftStockChange } = useViewModelInstanceNumber(
'microsoft/stockChange',
viewModelInstance
);
// Tesla Values
const { setValue: setTeslaName } = useViewModelInstanceString(
'tesla/name',
viewModelInstance
);
const { setValue: setTeslaStockChange } = useViewModelInstanceNumber(
'tesla/stockChange',
viewModelInstance
);
useEffect(() => {
// Set initial values for the view model
if (
setTitle &&
setLogoShape &&
setRootColor &&
setAppleName &&
setMicrosoftName &&
setTeslaName
) {
setTitle('Rive Stocks Dashboard');
setLogoShape('triangle');
setRootColor(parseInt('ffc0ffee', 16));
setAppleName('AAPL');
setMicrosoftName('MSFT');
setTeslaName('TSLA');
}
// randomly generate stock values every 2 seconds
const interval = setInterval(() => {
const appleValue = randomValue();
const microsoftValue = randomValue();
const teslaValue = randomValue();
setAppleStockChange(appleValue);
setMicrosoftStockChange(microsoftValue);
setTeslaStockChange(teslaValue);
// If all the stock values are either all positive or all negative, spin the logo
if (
(appleValue > 0 && microsoftValue > 0 && teslaValue > 0) ||
(appleValue < 0 && microsoftValue < 0 && teslaValue < 0)
) {
triggerSpinLogo();
}
}, 2000);
return () => clearInterval(interval);
}, [
setTitle,
setLogoShape,
setRootColor,
setAppleName,
setMicrosoftName,
setTeslaName,
setAppleStockChange,
setMicrosoftStockChange,
setTeslaStockChange,
triggerSpinLogo,
]);
// listen for changes to the AAPL color and log them
useEffect(() => {
if (appleColor) {
console.log('Apple color changed:', appleColor);
}
}, [appleColor]);
return <RiveComponent />;
};
export default DataBinding;

View File

@@ -1,348 +0,0 @@
import React, { useEffect } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { within, expect, waitFor, userEvent } from '@storybook/test';
import { StringPropertyTest, NumberPropertyTest, BooleanPropertyTest, ColorPropertyTest, EnumPropertyTest, NestedViewModelTest, TriggerPropertyTest, PersonForm, PersonInstances } from './DataBindingTests';
const meta: Meta = {
title: 'Tests/DataBinding',
parameters: {
layout: 'centered',
},
};
export default meta;
export const StringPropertyStory: StoryObj = {
name: 'String Property',
render: () => <StringPropertyTest src="person_databinding_test.riv" />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for the Rive file to load
await waitFor(() => {
expect(canvas.getByTestId('name-value')).toBeTruthy();
}, { timeout: 3000 });
const nameInput = canvas.getByTestId<HTMLInputElement>('name-input');
await userEvent.clear(nameInput);
// Wait for the input to be cleared
await waitFor(() => {
expect(nameInput.value).toBe('');
}, { timeout: 1000 });
await userEvent.click(nameInput);
await userEvent.paste('Test User');
await waitFor(() => {
expect(nameInput.value).toBe('Test User');
}, { timeout: 2000 });
await waitFor(() => {
expect(canvas.getByTestId('name-value').textContent).toBe('Test User');
}, { timeout: 2000 });
}
};
export const NumberPropertyStory: StoryObj = {
name: 'Number Property',
render: () => <NumberPropertyTest src="person_databinding_test.riv" />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for the Rive file to load
await waitFor(() => {
expect(canvas.getByTestId('age-value')).toBeTruthy();
}, { timeout: 2000 });
const ageInput = canvas.getByTestId<HTMLInputElement>('age-input');
const currentValue = ageInput.value;
expect(currentValue).toBe('23');
await userEvent.click(ageInput);
await userEvent.clear(ageInput);
await waitFor(() => {
expect(ageInput.value).toBe('0'); // This is a hack to wait for the input to be cleared
}, { timeout: 1000 });
await userEvent.paste('42');
await waitFor(() => {
expect(canvas.getByTestId('age-value').textContent).toBe('42');
}, { timeout: 2000 });
}
};
export const BooleanPropertyStory: StoryObj = {
name: 'Boolean Property',
render: () => <BooleanPropertyTest src="person_databinding_test.riv" />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for the Rive file to load
await waitFor(() => {
expect(canvas.getByTestId('terms-value')).toBeTruthy();
}, { timeout: 2000 });
const termsCheckbox = canvas.getByTestId<HTMLInputElement>('terms-checkbox');
expect(termsCheckbox.checked).toBe(false);
expect(canvas.getByTestId('terms-value').textContent).toBe('false');
await userEvent.click(termsCheckbox);
// Verify terms update
await waitFor(() => {
expect(canvas.getByTestId('terms-value').textContent).toBe('true');
});
}
};
export const ColorPropertyStory: StoryObj = {
name: 'Color Property',
render: () => <ColorPropertyTest src="person_databinding_test.riv" />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for the Rive file to load and the component to render
await waitFor(() => {
expect(canvas.getByTestId('color-value')).toBeTruthy();
expect(canvas.getByTestId('set-color-red')).toBeTruthy();
expect(canvas.getByTestId('set-color-blue')).toBeTruthy();
}, { timeout: 5000 });
const numberValueDiv = canvas.getByTestId('number-value');
const hexValueDiv = canvas.getByTestId('hex-value');
// Verify initial state is red
await waitFor(() => {
expect(hexValueDiv.textContent).toContain('Hex value: #ce2323');
expect(numberValueDiv.textContent).toContain('Number value: -3267805');
});
// Change color to Blue ---
const blueButton = canvas.getByTestId('set-color-blue');
await userEvent.click(blueButton);
// Verify Blue State
await waitFor(() => {
expect(numberValueDiv.textContent).toContain('Number value: -16776961');
expect(hexValueDiv.textContent).toContain('Hex value: #0000ff');
});
// Change color back to Red ---
const redButton = canvas.getByTestId('set-color-red');
await userEvent.click(redButton);
// Verify Red State
await waitFor(() => {
expect(numberValueDiv.textContent).toContain('Number value: -65536');
expect(hexValueDiv.textContent).toContain('Hex value: #ff0000');
});
}
};
export const EnumPropertyStory: StoryObj = {
name: 'Enum Property',
render: () => <EnumPropertyTest src="person_databinding_test.riv" />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for the Rive file to load
await waitFor(() => {
expect(canvas.getByTestId('country-value')).toBeTruthy();
});
// Wait for options to be loaded
await waitFor(() => {
const countrySelect = canvas.getByTestId<HTMLSelectElement>('country-select');
return countrySelect.options.length > 0;
});
const countrySelect = canvas.getByTestId<HTMLSelectElement>('country-select');
// Verify that the dropdown contains usa, japan, and canada
const optionValues = Array.from(countrySelect.options).map(option => option.value);
expect(optionValues).toContain('usa');
expect(optionValues).toContain('japan');
expect(optionValues).toContain('canada');
const currentValue = countrySelect.value;
expect(currentValue).toBe('usa');
let optionToSelect = 'japan';
await userEvent.selectOptions(countrySelect, optionToSelect);
await waitFor(() => {
expect(canvas.getByTestId('country-value').textContent).toBe(optionToSelect);
});
}
};
export const NestedViewModelStory: StoryObj = {
name: 'Nested ViewModel Property',
render: () => <NestedViewModelTest src="person_databinding_test.riv" />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for the Rive file to load
await waitFor(() => {
expect(canvas.getByTestId('drink-type-value')).toBeTruthy();
});
// Wait for options to be loaded
await waitFor(() => {
const drinkTypeSelect = canvas.getByTestId<HTMLSelectElement>('drink-type-select');
return drinkTypeSelect.options.length > 0;
}, { timeout: 2000 });
const drinkTypeSelect = canvas.getByTestId<HTMLSelectElement>('drink-type-select');
const optionValues = Array.from(drinkTypeSelect.options).map(option => option.value);
expect(optionValues).toContain('Coffee');
expect(optionValues).toContain('Tea');
expect(drinkTypeSelect.value).toBe('Tea');
let optionToSelect = 'Coffee';
await userEvent.selectOptions(drinkTypeSelect, optionToSelect);
await waitFor(() => {
expect(canvas.getByTestId('drink-type-value').textContent).toBe(optionToSelect);
});
}
};
export const TriggerPropertyStory: StoryObj = {
name: 'Trigger Property',
render: () => <TriggerPropertyTest src="person_databinding_test.riv" />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for the Rive file to load
await waitFor(() => {
expect(canvas.getByTestId('submit-button')).toBeTruthy();
}, { timeout: 2000 });
expect(canvas.getByTestId('callback-triggered').textContent).toContain('none');
// Trigger submit action
await userEvent.click(canvas.getByTestId('submit-button'));
await waitFor(() => {
expect(canvas.getByTestId('callback-triggered').textContent).toContain('submit-callback');
});
await userEvent.click(canvas.getByTestId('reset-button'));
// Verify onTrigger callback works for reset
await waitFor(() => {
expect(canvas.getByTestId('callback-triggered').textContent).toContain('reset-callback');
});
}
};
export const PersonInstancesStory: StoryObj = {
name: 'Person Instances',
render: () => <PersonInstances src="person_databinding_test.riv" />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for the Rive file to load
await waitFor(() => {
expect(canvas.getByTestId('instance-name')).toBeTruthy();
expect(canvas.getByTestId('select-jane')).toBeTruthy();
expect(canvas.getByTestId('select-default')).toBeTruthy();
}, { timeout: 2000 });
// Initially should show Steve
expect(canvas.getByTestId('instance-name').textContent).toContain('Steve');
// Switch to Jane
const janeButton = canvas.getByTestId('select-jane');
await userEvent.click(janeButton);
// Verify instance changed to Jane
await waitFor(() => {
expect(canvas.getByTestId('instance-name').textContent).toContain('Jane');
});
// Switch to Default instance
const defaultButton = canvas.getByTestId('select-default');
await userEvent.click(defaultButton);
// Verify instance changed to Default
await waitFor(() => {
expect(canvas.getByTestId('instance-name').textContent).toContain('Default');
});
// Switch back to Steve
const steveButton = canvas.getByTestId('select-steve');
await userEvent.click(steveButton);
// Verify instance changed back to Steve
await waitFor(() => {
expect(canvas.getByTestId('instance-name').textContent).toContain('Steve');
});
}
};
// A configurable form story, so we can test all the properties at once
export const PersonFormStory: StoryObj = {
name: 'Complete Person Form',
render: () => <PersonForm src="person_databinding_test.riv" />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for the Rive file to load
await waitFor(() => {
expect(canvas.getByTestId('name-value')).toBeTruthy();
}, { timeout: 2000 });
// Update name
const nameInput = canvas.getByTestId('name-input');
await userEvent.clear(nameInput);
await userEvent.type(nameInput, 'Test User');
// Update age
const ageInput = canvas.getByTestId('age-input');
await userEvent.clear(ageInput);
await userEvent.type(ageInput, '42');
// Toggle terms agreement
const termsCheckbox = canvas.getByTestId('terms-checkbox');
await userEvent.click(termsCheckbox);
// Change color
const colorButton = canvas.getByTestId('set-color-red');
await userEvent.click(colorButton);
// Change country
const countrySelect = canvas.getByTestId<HTMLSelectElement>('country-select');
await userEvent.selectOptions(countrySelect, 'japan');
// Change drink type
const drinkTypeSelect = canvas.getByTestId<HTMLSelectElement>('drink-type-select');
await userEvent.selectOptions(drinkTypeSelect, 'Coffee');
// Submit the form
const submitButton = canvas.getByTestId('submit-button');
await userEvent.click(submitButton);
}
};

View File

@@ -1,524 +0,0 @@
import React, { useEffect, useState } from 'react';
import Rive, {
useRive,
useViewModel,
useViewModelInstance,
useViewModelInstanceBoolean,
useViewModelInstanceString,
useViewModelInstanceNumber,
useViewModelInstanceEnum,
useViewModelInstanceColor,
useViewModelInstanceTrigger
} from '@rive-app/react-webgl2';
export const StringPropertyTest = ({ src }: { src: string }) => {
const { rive, RiveComponent } = useRive({
src,
autoplay: true,
artboard: "Artboard",
autoBind: true,
stateMachines: "State Machine 1",
});
const { value: name, setValue: setName } = useViewModelInstanceString('name', rive?.viewModelInstance);
return (
<div>
<RiveComponent style={{ width: '400px', height: '400px' }} />
{(rive === null) ? <div data-testid="loading-text">Loading</div> : (
<div>
<label>
Name:
<input
data-testid="name-input"
type="text"
value={name || ''}
onChange={(e) => setName(e.target.value)}
autoFocus={false}
/>
</label>
<div data-testid="name-value">{name}</div>
</div>
)}
</div>
);
};
export const NumberPropertyTest = ({ src }: { src: string }) => {
const { rive, RiveComponent } = useRive({
src,
autoplay: true,
artboard: "Artboard",
autoBind: true,
stateMachines: "State Machine 1",
});
const { value: age, setValue: setAge } = useViewModelInstanceNumber('age', rive?.viewModelInstance);
return (
<div>
<RiveComponent style={{ width: '400px', height: '400px' }} />
{(rive === null) ? <div data-testid="loading-text">Loading</div> : (
<div>
<label>
Age:
<input
data-testid="age-input"
type="number"
value={age ?? 0}
onChange={(e) => setAge(Number(e.target.value))}
autoFocus={false}
/>
</label>
<div data-testid="age-value">{age}</div>
</div>
)}
</div>
);
};
export const BooleanPropertyTest = ({ src }: { src: string }) => {
const { rive, RiveComponent } = useRive({
src,
autoplay: true,
artboard: "Artboard",
autoBind: true,
stateMachines: "State Machine 1",
});
const { value: agreedToTerms, setValue: setAgreedToTerms } = useViewModelInstanceBoolean('agreedToTerms', rive?.viewModelInstance);
return (
<div>
<RiveComponent style={{ width: '400px', height: '400px' }} />
{(rive === null) ? <div data-testid="loading-text">Loading</div> : (
<div>
<label>
<input
data-testid="terms-checkbox"
type="checkbox"
checked={agreedToTerms ?? false}
onChange={(e) => setAgreedToTerms(e.target.checked)}
/>
Agree to Terms
</label>
<div data-testid="terms-value">{agreedToTerms ? 'true' : 'false'}</div>
</div>
)}
</div>
);
};
const colorNumberToHexString = (colorNum: number | null) => {
if (colorNum === null) {
return 'N/A';
}
const unsignedInt = colorNum >>> 0;
const r = (unsignedInt >> 16) & 0xff;
const g = (unsignedInt >> 8) & 0xff;
const b = unsignedInt & 0xff;
const toHex = (c: number) => c.toString(16).padStart(2, '0');
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
};
export const ColorPropertyTest = ({ src }: { src: string }) => {
const { rive, RiveComponent } = useRive({
src,
autoplay: true,
artboard: "Artboard",
autoBind: true,
stateMachines: "State Machine 1",
});
const { value: colorNum, setValue: setColor, setRgb } = useViewModelInstanceColor('favColor', rive?.viewModelInstance);
return (
<div>
<RiveComponent style={{ width: '400px', height: '400px' }} />
{(rive === null) ? <div data-testid="loading-text">Loading</div> : (
<div>
<label>
Favorite Color:
<div data-testid="color-value" style={{
backgroundColor: typeof colorNum === 'string' ? colorNum : colorNumberToHexString(colorNum),
width: '20px',
height: '20px',
display: 'inline-block',
marginLeft: '10px'
}}></div>
<div data-testid="number-value">
Number value: {typeof colorNum === 'number' ? colorNum : 'N/A'}
</div>
<div data-testid="hex-value">
Hex value: {colorNumberToHexString(colorNum)}
</div>
</label>
<button
data-testid="set-color-red"
type="button"
onClick={() => setRgb(255, 0, 0)}
>
Red
</button>
<button
data-testid="set-color-blue"
type="button"
onClick={() => setRgb(0, 0, 255)}
>
Blue
</button>
</div>
)}
</div>
);
};
export const EnumPropertyTest = ({ src }: { src: string }) => {
const { rive, RiveComponent } = useRive({
src,
autoplay: true,
artboard: "Artboard",
autoBind: true,
stateMachines: "State Machine 1",
});
const { value: country, setValue: setCountry, values: countries } = useViewModelInstanceEnum('country', rive?.viewModelInstance);
return (
<div>
<RiveComponent style={{ width: '400px', height: '400px' }} />
{(rive === null) ? <div data-testid="loading-text">Loading</div> : (
<div>
<label>
Country:
<select
data-testid="country-select"
value={country || ''}
onChange={(e) => setCountry(e.target.value)}
>
{countries.map(c => (
<option key={c} value={c}>{c}</option>
))}
</select>
</label>
<div data-testid="country-value">{country}</div>
</div>
)}
</div>
);
};
export const NestedViewModelTest = ({ src }: { src: string }) => {
const { rive, RiveComponent } = useRive({
src,
autoplay: true,
artboard: "Artboard",
autoBind: true,
stateMachines: "State Machine 1",
});
const { value: drinkType, setValue: setDrinkType, values: drinkTypes } = useViewModelInstanceEnum('favDrink/type', rive?.viewModelInstance);
return (
<div>
<RiveComponent style={{ width: '400px', height: '400px' }} />
{(rive === null) ? <div data-testid="loading-text">Loading</div> : (
<div>
<label>
Favorite Drink Type:
<select
data-testid="drink-type-select"
value={drinkType || ''}
onChange={(e) => setDrinkType(e.target.value)}
>
{drinkTypes.map(dt => (
<option key={dt} value={dt}>{dt}</option>
))}
</select>
</label>
<div data-testid="drink-type-value">{drinkType}</div>
</div>
)}
</div>
);
};
export const TriggerPropertyTest = ({ src }: { src: string }) => {
const [callbackTriggered, setCallbackTriggered] = useState('');
const { rive, RiveComponent } = useRive({
src,
autoplay: true,
autoBind: true,
artboard: "Artboard",
stateMachines: "State Machine 1",
});
const { trigger: onFormSubmit } = useViewModelInstanceTrigger('onFormSubmit', rive?.viewModelInstance,
{
onTrigger: () => {
setCallbackTriggered('submit-callback');
}
}
);
const { trigger: onFormReset } = useViewModelInstanceTrigger('onFormReset', rive?.viewModelInstance,
{
onTrigger: () => {
setCallbackTriggered('reset-callback');
}
}
);
const handleSubmit = () => {
onFormSubmit();
};
const handleReset = () => {
onFormReset();
};
return (
<div>
<RiveComponent style={{ width: '400px', height: '400px' }} />
{(rive === null) ? <div data-testid="loading-text">Loading</div> : (
<div>
<button data-testid="submit-button" type="button" onClick={handleSubmit}>Submit</button>
<button data-testid="reset-button" type="button" onClick={handleReset}>Reset</button>
<div data-testid="callback-triggered">
Last callback triggered: {callbackTriggered || 'none'}
</div>
</div>
)}
</div>
);
};
export const PersonForm = ({ src }: { src: string }) => {
const { rive, RiveComponent } = useRive({
src,
autoplay: true,
autoBind: true,
artboard: "Artboard",
stateMachines: "State Machine 1",
});
const { value: name, setValue: setName } = useViewModelInstanceString('name', rive?.viewModelInstance);
const { value: age, setValue: setAge } = useViewModelInstanceNumber('age', rive?.viewModelInstance);
const { value: agreedToTerms, setValue: setAgreedToTerms } = useViewModelInstanceBoolean('agreedToTerms', rive?.viewModelInstance);
const { value: colorNum, setRgb } = useViewModelInstanceColor('favColor', rive?.viewModelInstance);
const { value: country, setValue: setCountry, values: countries } = useViewModelInstanceEnum('country', rive?.viewModelInstance);
const { trigger: onFormSubmit } = useViewModelInstanceTrigger('onFormSubmit', rive?.viewModelInstance);
const { trigger: onFormReset } = useViewModelInstanceTrigger('onFormReset', rive?.viewModelInstance);
// Drink properties (nested viewmodel)
const { value: drinkType, setValue: setDrinkType, values: drinkTypes } = useViewModelInstanceEnum('favDrink/type', rive?.viewModelInstance);
const handleReset = () => {
setName('');
setAge(0);
setAgreedToTerms(false);
setRgb(0, 0, 0);
setCountry(countries[0]);
setDrinkType(drinkTypes[0]);
onFormReset();
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onFormSubmit();
};
return (
<div>
<RiveComponent style={{ width: '400px', height: '400px' }} />
{(rive === null) ? <div data-testid="loading-text">Loading</div> : (
<form onSubmit={handleSubmit}>
<div>
<label>
Name:
<input
data-testid="name-input"
type="text"
value={name || ''}
onChange={(e) => setName(e.target.value)}
/>
</label>
<div data-testid="name-value">{name}</div>
</div>
<div>
<label>
Age:
<input
data-testid="age-input"
type="number"
value={age || 0}
onChange={(e) => setAge(Number(e.target.value))}
/>
</label>
<div data-testid="age-value">{age}</div>
</div>
<div>
<label>
<input
data-testid="terms-checkbox"
type="checkbox"
checked={agreedToTerms || false}
onChange={(e) => setAgreedToTerms(e.target.checked)}
/>
Agree to Terms
</label>
<div data-testid="terms-value">{agreedToTerms ? 'true' : 'false'}</div>
</div>
<div>
<label>
Favorite Color:
<div data-testid="color-value" style={{
backgroundColor: colorNumberToHexString(colorNum),
width: '20px',
height: '20px',
display: 'inline-block',
marginLeft: '10px'
}}></div>
</label>
<button
data-testid="set-color-red"
type="button"
onClick={() => setRgb(255, 0, 0)}
>
Red
</button>
<button
data-testid="set-color-blue"
type="button"
onClick={() => setRgb(0, 0, 255)}
>
Blue
</button>
</div>
<div>
<label>
Country:
<select
data-testid="country-select"
value={country || ''}
onChange={(e) => setCountry(e.target.value)}
>
{countries.map(c => (
<option key={c} value={c}>{c}</option>
))}
</select>
</label>
<div data-testid="country-value">{country}</div>
</div>
<div>
<label>
Favorite Drink Type:
<select
data-testid="drink-type-select"
value={drinkType || ''}
onChange={(e) => setDrinkType(e.target.value)}
>
{drinkTypes.map(dt => (
<option key={dt} value={dt}>{dt}</option>
))}
</select>
</label>
<div data-testid="drink-type-value">{drinkType}</div>
</div>
<div>
<button data-testid="submit-button" type="submit">Submit</button>
<button data-testid="reset-button" type="button" onClick={handleReset}>Reset</button>
</div>
</form>
)}
</div>
);
};
// Component to demonstrate different viewmodel instances
export const PersonInstances = ({ src }: { src: string }) => {
const [activeInstance, setActiveInstance] = useState('Steve');
const [useDefaultInstance, setUseDefaultInstance] = useState(false);
const { rive, RiveComponent } = useRive({
src,
autoplay: true,
artboard: "Artboard",
stateMachines: "State Machine 1",
});
const viewModel = useViewModel(rive, { name: 'PersonViewModel' });
const params = useDefaultInstance ? { useDefault: true, rive } : { name: activeInstance, rive }
const viewModelInstance = useViewModelInstance(viewModel, params);
const { value: name } = useViewModelInstanceString('name', viewModelInstance);
const { value: age } = useViewModelInstanceNumber('age', viewModelInstance);
const { value: country } = useViewModelInstanceEnum('country', viewModelInstance);
const switchToNamedInstance = (instanceName: string) => {
setActiveInstance(instanceName);
setUseDefaultInstance(false);
};
const switchToDefaultInstance = () => {
setUseDefaultInstance(true);
};
return (
<div>
<RiveComponent style={{ width: '400px', height: '400px' }} />
{(rive === null) ? <div data-testid="loading-text">Loading</div> : (
<div>
<button
data-testid="select-steve"
onClick={() => switchToNamedInstance('Steve')}
disabled={!useDefaultInstance && activeInstance === 'Steve'}
>
Steve
</button>
<button
data-testid="select-jane"
onClick={() => switchToNamedInstance('Jane')}
disabled={!useDefaultInstance && activeInstance === 'Jane'}
>
Jane
</button>
<button
data-testid="select-default"
onClick={switchToDefaultInstance}
disabled={useDefaultInstance}
>
Default
</button>
</div>
)}
<div>
<h3 data-testid="instance-name">Instance: {useDefaultInstance ? 'Default' : activeInstance}</h3>
<p data-testid="person-name">Name: {name}</p>
<p data-testid="person-age">Age: {age}</p>
<p data-testid="person-country">Country: {country}</p>
</div>
</div>
);
};

View File

@@ -1,17 +0,0 @@
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 = {};

View File

@@ -1,34 +0,0 @@
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;

View File

@@ -1,17 +0,0 @@
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 = {};

View File

@@ -1,14 +0,0 @@
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;

View File

@@ -1,17 +0,0 @@
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 = {};

View File

@@ -1,18 +0,0 @@
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;

View File

@@ -1,17 +0,0 @@
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 = {};

View File

@@ -1,14 +0,0 @@
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;

View File

@@ -0,0 +1,14 @@
# 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
```

View File

@@ -0,0 +1,36 @@
{
"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"
]
}
}

View File

@@ -0,0 +1,17 @@
<!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>

View File

Binary file not shown.

View File

@@ -0,0 +1,58 @@
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;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -0,0 +1,14 @@
# 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
```

View File

@@ -0,0 +1,36 @@
{
"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"
]
}
}

View File

@@ -0,0 +1,17 @@
<!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>

View File

Binary file not shown.

View File

@@ -0,0 +1,34 @@
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;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -0,0 +1,14 @@
# 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
```

View File

@@ -0,0 +1,36 @@
{
"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"
]
}
}

View File

@@ -0,0 +1,17 @@
<!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>

View File

Binary file not shown.

View File

@@ -0,0 +1,31 @@
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;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +0,0 @@
# @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.

View File

@@ -1,26 +0,0 @@
{
"name": "@rive-app/react-canvas-lite",
"version": "4.20.2",
"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.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
}
}

View File

@@ -1,3 +0,0 @@
# @rive-app/react-canvas
Output for `rive-react` using the backing `@rive-app/canvas` JS runtime

View File

@@ -1,26 +0,0 @@
{
"name": "@rive-app/react-canvas",
"version": "4.20.2",
"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.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
}
}

View File

@@ -1,3 +0,0 @@
# @rive-app/react-webgl
Output for `rive-react` using the backing `@rive-app/webgl` JS runtime

View File

@@ -1,26 +0,0 @@
{
"name": "@rive-app/react-webgl",
"version": "4.20.2",
"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.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
}
}

View File

View File

@@ -1,26 +0,0 @@
{
"name": "@rive-app/react-webgl2",
"version": "4.20.2",
"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.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
}
}

19308
package-lock.json generated Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "rive-react",
"version": "4.20.2",
"version": "0.0.26",
"description": "React wrapper around the rive-js library",
"main": "dist/index.js",
"typings": "dist/types/index.d.ts",
@@ -14,15 +14,7 @@
"lint": "eslint -c .eslintrc.js 'src/**/*{.ts,.tsx}'",
"format": "prettier --write src",
"types:check": "tsc --noEmit",
"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"
"release": "release-it"
},
"repository": {
"type": "git",
@@ -35,27 +27,22 @@
},
"homepage": "https://github.com/rive-app/rive-react#readme",
"dependencies": {
"@rive-app/canvas": "2.27.5",
"@rive-app/canvas-lite": "2.27.5",
"@rive-app/webgl": "2.27.5",
"@rive-app/webgl2": "2.27.5"
"@rive/webgl_single": "file:../rive-wasm/js/npm/webgl_single"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
"react": "^16.8.0 || ^17.0.0"
},
"devDependencies": {
"@babel/core": "^7.18.0",
"@testing-library/jest-dom": "^5.13.0",
"@testing-library/react": "^16.3.0",
"@types/jest": "^27.0.3",
"@types/offscreencanvas": "^2019.6.4",
"@types/react": "^18.0.0",
"@testing-library/react": "^11.2.7",
"@testing-library/react-hooks": "^7.0.0",
"@types/jest": "^26.0.23",
"@types/react": "^17.0.9",
"@types/testing-library__jest-dom": "^5.9.5",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
"auto-changelog": "^2.3.0",
"babel-loader": "^8.2.5",
"bunchee": "1.8.5",
"bunchee": "^1.7.3",
"eslint": "^7.28.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.3",
@@ -63,16 +50,13 @@
"eslint-plugin-node": "^11.1.0",
"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",
"eslint-plugin-react": "^7.24.0",
"jest": "^27.0.4",
"prettier": "^2.3.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"release-it": "^14.10.0",
"ts-jest": "^27.1.1",
"typescript": "^4.5.4",
"ts-jest": "^27.0.2",
"watch": "^1.0.2"
}
}

View File

@@ -1,42 +0,0 @@
#!/bin/bash
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
if [[ "$OSTYPE" == "darwin"* ]]; then
find . -type f -name "*.ts" -print0 | xargs -0 sed -i '' -e 's/@rive-app\/canvas/@rive-app\/webgl/g'
find . -type f -name "*.js" -print0 | xargs -0 sed -i '' -e 's/@rive-app\/canvas/@rive-app\/webgl/g'
else
find . -type f -name "*.ts" -print0 | xargs -0 sed -i -e 's/@rive-app\/canvas/@rive-app\/webgl/g'
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

View File

@@ -1,10 +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
echo Publishing `echo $dir | sed 's:.*/::'`
npm publish $@
popd > /dev/null
done

View File

@@ -1,24 +0,0 @@
#!/bin/bash
set -e
echo "Copying package.json to rive-react npm package folders"
# 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
../../scripts/setup_package.sh $repo_name
echo Finished setting up package.json
popd > /dev/null
done

View File

@@ -1,6 +0,0 @@
#!/bin/bash
set -e
# Setup the package.json for a given npm module
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
node $SCRIPT_DIR/trimPackageJson.js `pwd` "$1"

View File

@@ -1,23 +0,0 @@
const fs = require('fs');
const path = process.argv[2];
const npmPackageSplit = process.argv[3].split('-');
// extracts "webgl" or "canvas-lite" from the npm package name
const renderer = npmPackageSplit.slice(1).join('-');
const package = require(path + '/package.json');
function trimNpmPackage() {
package.name = `@rive-app/react-${renderer}`;
package.description = `React wrapper around the @rive-app/${renderer} library`;
const webDependencyName = `@rive-app/${renderer}`;
const canvasDep = package.dependencies[webDependencyName];
package.dependencies = {
[webDependencyName]: canvasDep,
};
delete package.devDependencies;
delete package.scripts;
fs.writeFileSync(path + '/package.json', JSON.stringify(package, null, 2));
}
if (renderer) {
trimNpmPackage();
}

Some files were not shown because too many files have changed in this diff Show More