bible-strong-avatar-lab/examples/react-vite-consumer/src/main.tsx
smontlouis 2386b78817 feat(runtime): unify React and ESM avatar exports
Add shared core-backed React and browser runtimes, continuous playback transitions, export previews and API documentation. Package the JavaScript export as a CDN-powered demo with the portable avatar definition.
2026-08-17 17:27:20 +02:00

102 lines
3.6 KiB
TypeScript

import { validateAvatarDefinition, type AvatarDefinition } from '@bible-strong/avatar-core'
import { Avatar, createAvatar } from '@bible-strong/avatar-react'
import '@bible-strong/avatar-react/styles.css'
import { StrictMode, useState } from 'react'
import { createRoot } from 'react-dom/client'
import definitionJson from './strobi.avatar.json'
import './styles.css'
const validation = validateAvatarDefinition(definitionJson)
if (!validation.ok) throw new Error(validation.errors[0]?.message)
const definition = validation.value as AvatarDefinition
const StrobiAvatar = createAvatar(definitionJson)
type StrobiExpressionKey = keyof typeof definitionJson.expressions
const expressions = Object.keys(definition.expressions) as StrobiExpressionKey[]
const horizontalPosition = (expression: string) =>
expression.includes('left') ? 'left' : expression.includes('right') ? 'right' : 'center'
const verticalPosition = (expression: string) =>
expression.includes('up') ? 'up' : expression.includes('down') ? 'down' : 'middle'
const expressionGroups = [
['up', 'left', 'Up left'],
['up', 'center', 'Up'],
['up', 'right', 'Up right'],
['middle', 'left', 'Left'],
['middle', 'center', 'Neutral'],
['middle', 'right', 'Right'],
['down', 'left', 'Down left'],
['down', 'center', 'Down'],
['down', 'right', 'Down right'],
].map(([vertical, horizontal, label]) => ({
key: `${vertical}-${horizontal}`,
label,
expressions: expressions.filter(
expression =>
verticalPosition(expression) === vertical && horizontalPosition(expression) === horizontal
),
}))
const formatExpressionName = (expression: string) =>
expression.replaceAll('-', ' ').replace(/\b\w/g, letter => letter.toUpperCase())
function Demo() {
const [expression, setExpression] = useState<StrobiExpressionKey>('neutral')
return (
<main>
<h1>Avatar package consumer</h1>
<section className="avatar-host" aria-label="Embedded avatar example">
<Avatar
definition={definition}
expression={expression}
size={240}
ariaLabel="Embedded Strobi avatar"
/>
</section>
<section className="expression-picker" aria-labelledby="expression-picker-title">
<div className="expression-picker__heading">
<h2 id="expression-picker-title">Available expressions ({expressions.length})</h2>
<output aria-live="polite">Active: {formatExpressionName(expression)}</output>
</div>
<div className="expression-picker__list">
{expressionGroups.map(group => (
<section
key={group.key}
className={`expression-picker__group expression-picker__group--${group.key}`}
>
<h3>{group.label}</h3>
<div className="expression-picker__group-list">
{group.expressions.map(key => (
<button
key={key}
type="button"
className={key === expression ? 'is-selected' : undefined}
aria-pressed={key === expression}
onClick={() => setExpression(key)}
>
{formatExpressionName(key)}
</button>
))}
</div>
</section>
))}
</div>
</section>
<div className="avatar-overlay" aria-label="Positioned avatar example">
<Avatar
definition={definition}
expression={expression}
size={128}
ariaLabel="Positioned Strobi avatar"
/>
</div>
</main>
)
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Demo />
</StrictMode>
)