mirror of
https://github.com/rive-app/rive-react.git
synced 2026-03-13 08:22:30 +08:00
feat: add db test
This commit is contained in:
@@ -17,7 +17,8 @@
|
||||
},
|
||||
"scripts": {
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
"build-storybook": "storybook build",
|
||||
"test-storybook": "test-storybook"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -47,8 +48,9 @@
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
examples/public/person_databinding_test.riv
Normal file
BIN
examples/public/person_databinding_test.riv
Normal file
Binary file not shown.
348
examples/src/components/DataBindingTests.stories.tsx
Normal file
348
examples/src/components/DataBindingTests.stories.tsx
Normal file
@@ -0,0 +1,348 @@
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
524
examples/src/components/DataBindingTests.tsx
Normal file
524
examples/src/components/DataBindingTests.tsx
Normal file
@@ -0,0 +1,524 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
5002
examples/yarn.lock
5002
examples/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user