React UI
How a plugin UI is written with React: components, styles, events, state and parameters.
Source: packages/react/.
A plugin's UI is a React app. @soundor/react renders it into the plugin
view (soundor:ui), with the components and conventions of React Native:
import { Pressable, Text, View, useParameter } from '@soundor/react';
import { parameters } from 'soundor:parameters';
// A parameter's value, re-rendered whenever it changes: here, from the
// buttons; in a plugin, from host automation and presets too.
export default function Gain() {
const gain = useParameter(parameters.gain);
const step = (by: number) => parameters.gain.set(gain + by);
return (
<View style={{ alignItems: 'center', gap: 12 }}>
<Text style={{ fontSize: 28, color: '#e6e7ea' }}>
Gain {gain.toFixed(2)}
</Text>
<View style={{ flexDirection: 'row', gap: 8 }}>
{[
{ label: '−', onPress: () => step(-0.1) },
{ label: 'Reset', onPress: () => parameters.gain.set(0.5) },
{ label: '+', onPress: () => step(0.1) },
].map(({ label, onPress }) => (
<Pressable
key={label}
onPress={onPress}
style={({ pressed, hovered }) => ({
minWidth: 44,
alignItems: 'center',
paddingVertical: 8,
paddingHorizontal: 14,
borderRadius: 8,
backgroundColor: pressed
? '#2a5fd0'
: hovered
? '#4f8bff'
: '#3a7bfd',
})}
>
<Text style={{ color: 'white', fontSize: 15 }}>{label}</Text>
</Pressable>
))}
</View>
</View>
);
}gain.tsxSourceThe snippets on this site run live, in the web runtime: edit one, then
apply. A plugin's src/main.tsx renders its root component with
render(<App />).
No DOM
In a native plugin the UI runs inside Soundor's embedded JavaScript runtime,
without a browser: there is no DOM, no window, no CSS. The web runtime
draws the same Soundor nodes with DOM elements. Either way the UI sees the
same components, the same layout and the same events, so it is written once:
- Components are React Native's:
View,Text,Pressable,TextInput,ScrollView,Image, plusCanvasfor code that draws, and overlays:Portal,Modal,FocusScope. Strings go in aText. - Styles are objects, not CSS: flexbox, a column by default (Style).
- Events are Web-style pointer, wheel, key and focus events that capture and bubble (Events), in the view's logical pixels (coordinates).
- Accessibility is React Native's props, which every runtime presents to its platform's screen readers (Accessibility).
- Animation runs in the runtime, not in React:
Animated.Viewanimates style changes, anduseAnimationFramedraws a canvas every frame.
State and parameters
What the host automates and saves is a parameter, not React state: read one
with useParameter, and change it with set(), so the host, its automation
and presets, and every view show the same value.
const gain = useParameter(parameters.gain);
// …
parameters.gain.beginGesture();
parameters.gain.set(next); // while dragging
parameters.gain.endGesture();Keep React state for what only the UI knows: a tab, a menu that is open.
Persistent preferences go in soundor:storage, files
in soundor:fs. See Parameters and
@soundor/react for rendering and the hooks.
Testing
Install vitest, @soundor/vitest, vitest-environment-soundor, and
@soundor/testing-library as direct development dependencies. Configure
plugins: [soundor()] from @soundor/vitest and test.environment: 'soundor'.
Tests run against a simulated Soundor runtime, using the real React reconciler.
import { Pressable, Text } from '@soundor/react';
import { render, screen, userEvent } from '@soundor/testing-library/react';
import { expect, test, vi } from 'vitest';
test('activates a control', async () => {
const onPress = vi.fn();
const user = userEvent.setup();
await render(
<Pressable onPress={onPress}>
<Text>Save</Text>
</Pressable>,
);
await user.click(screen.getByRole('button', { name: 'Save' }));
expect(onPress).toHaveBeenCalledOnce();
});See the Testing guide for installation, configuration,
queries, mocks, async updates, animation time, and limits. In this repository,
pnpm --filter @soundor/react test runs the private testing workspace, which
owns its direct testing dependencies and keeps the renderer's build independent
of its test tools.