Skip to content

Examples

Learn by example with these WebArcade plugin implementations.

Available Examples

Hello World

A minimal plugin that demonstrates the basic structure and API usage.

Features covered:

  • Plugin registration
  • Component registration with api.register()
  • Keyboard shortcuts

Full-Stack Plugin

A complete plugin with both frontend UI and Rust backend.

Features covered:

  • Rust HTTP handlers
  • Frontend-backend communication
  • Component registry
  • Toolbar and menus

Quick Reference

Minimal Plugin

jsx
import { plugin } from 'webarcade';

export default plugin({
    id: 'minimal',
    name: 'Minimal',
    version: '1.0.0',

    start(api) {
        api.register('main-view', {
            type: 'panel',
            component: () => <div class="p-4">Hello!</div>,
            label: 'Main'
        });
    }
});

Plugin with Sidebar

jsx
import { plugin } from 'webarcade';

function MainView() {
    return <div class="p-4">Main Content</div>;
}

function Sidebar() {
    return (
        <div class="p-4">
            <h3 class="font-bold mb-2">Navigation</h3>
            <ul class="menu">
                <li><a>Home</a></li>
                <li><a>About</a></li>
            </ul>
        </div>
    );
}

export default plugin({
    id: 'with-sidebar',
    name: 'With Sidebar',
    version: '1.0.0',

    start(api) {
        api.register('main', {
            type: 'panel',
            component: MainView,
            label: 'Main'
        });

        api.register('sidebar', {
            type: 'panel',
            component: Sidebar,
            label: 'Navigation',
            icon: IconList
        });
    }
});

Plugin with State

jsx
import { plugin } from 'webarcade';
import { createSignal } from 'solid-js';

const [count, setCount] = createSignal(0);

function Counter() {
    return (
        <div class="p-4">
            <h1 class="text-2xl">Count: {count()}</h1>
            <button class="btn btn-primary mt-4" onClick={() => setCount(c => c + 1)}>
                Increment
            </button>
        </div>
    );
}

export default plugin({
    id: 'counter',
    name: 'Counter',
    version: '1.0.0',

    start(api) {
        api.register('counter', {
            type: 'panel',
            component: Counter,
            label: 'Counter'
        });
    }
});

Plugin with Toolbar

jsx
import { plugin } from 'webarcade';
import { createSignal } from 'solid-js';
import { IconPlus, IconMinus, IconRefresh } from '@tabler/icons-solidjs';

const [count, setCount] = createSignal(0);

function CounterDisplay() {
    return (
        <div class="p-4 text-center">
            <h1 class="text-6xl font-bold">{count()}</h1>
        </div>
    );
}

export default plugin({
    id: 'toolbar-demo',
    name: 'Toolbar Demo',
    version: '1.0.0',

    start(api) {
        api.register('display', {
            type: 'panel',
            component: CounterDisplay,
            label: 'Counter'
        });

        api.register('increment', {
            type: 'toolbar',
            icon: IconPlus,
            tooltip: 'Increment',
            onClick: () => setCount(c => c + 1)
        });

        api.register('decrement', {
            type: 'toolbar',
            icon: IconMinus,
            tooltip: 'Decrement',
            onClick: () => setCount(c => c - 1)
        });

        api.register('reset', {
            type: 'toolbar',
            icon: IconRefresh,
            tooltip: 'Reset',
            onClick: () => setCount(0),
            disabled: () => count() === 0
        });
    }
});

Plugin with Shared Store

jsx
import { plugin } from 'webarcade';

function Settings(props) {
    const { api } = props;
    const theme = api.selector('settings.theme', 'light');

    return (
        <div class="p-4">
            <h1>Current theme: {theme()}</h1>
            <div class="flex gap-2 mt-4">
                <button
                    class="btn"
                    onClick={() => api.set('settings.theme', 'light')}
                >
                    Light
                </button>
                <button
                    class="btn"
                    onClick={() => api.set('settings.theme', 'dark')}
                >
                    Dark
                </button>
            </div>
        </div>
    );
}

export default plugin({
    id: 'settings',
    name: 'Settings',
    version: '1.0.0',

    start(api) {
        // Initialize default theme
        if (!api.has('settings.theme')) {
            api.set('settings.theme', 'light');
        }

        api.register('settings-panel', {
            type: 'panel',
            component: () => <Settings api={api} />,
            label: 'Settings'
        });

        // Watch theme changes
        api.watch('settings.theme', (theme) => {
            document.documentElement.setAttribute('data-theme', theme);
        });
    }
});

Plugin with Services

jsx
import { plugin } from 'webarcade';

// Provider plugin
export const audioPlugin = plugin({
    id: 'audio',
    name: 'Audio',
    version: '1.0.0',

    start(api) {
        const audioService = {
            play: (sound) => console.log('Playing:', sound),
            stop: () => console.log('Stopped'),
            setVolume: (v) => console.log('Volume:', v)
        };

        api.provide('audio', audioService);
    }
});

// Consumer plugin
export const gamePlugin = plugin({
    id: 'game',
    name: 'Game',
    version: '1.0.0',

    async start(api) {
        // Wait for audio service
        const audio = await api.use('audio');

        api.register('game-view', {
            type: 'panel',
            component: () => (
                <div class="p-4">
                    <button class="btn" onClick={() => audio.play('click')}>
                        Play Sound
                    </button>
                </div>
            ),
            label: 'Game'
        });
    }
});

Plugin with Keyboard Shortcuts

jsx
import { plugin } from 'webarcade';
import { createSignal } from 'solid-js';

const [logs, setLogs] = createSignal([]);

function addLog(msg) {
    setLogs(l => [...l.slice(-9), msg]);
}

function ShortcutDemo() {
    return (
        <div class="p-4">
            <h2 class="font-bold mb-2">Press shortcuts:</h2>
            <ul class="text-sm opacity-70 mb-4">
                <li>Ctrl+S - Save</li>
                <li>Ctrl+N - New</li>
                <li>Ctrl+Shift+P - Command Palette</li>
            </ul>
            <div class="bg-base-200 p-2 rounded">
                <For each={logs()}>
                    {(log) => <div class="text-sm">{log}</div>}
                </For>
            </div>
        </div>
    );
}

export default plugin({
    id: 'shortcuts-demo',
    name: 'Shortcuts Demo',
    version: '1.0.0',

    start(api) {
        api.register('demo', {
            type: 'panel',
            component: ShortcutDemo,
            label: 'Shortcuts'
        });

        api.shortcut({
            'ctrl+s': () => addLog('Save triggered'),
            'ctrl+n': () => addLog('New triggered'),
            'ctrl+shift+p': () => addLog('Command palette triggered')
        });
    }
});

Plugin with Contracts

jsx
import { plugin } from 'webarcade';

// File browser plugin with contracts
export default plugin({
    id: 'file-browser',
    name: 'File Browser',
    version: '1.0.0',

    start(api) {
        api.register('tree', {
            type: 'panel',
            component: FileTree,
            label: 'Files',
            contracts: {
                provides: ['file-browser', 'tree-view'],
                emits: ['file-selected', 'file-opened']
            }
        });

        // Emit events when files are selected
        function selectFile(path) {
            api.publish('file-selected', { path });
        }

        function openFile(path) {
            api.publish('file-opened', { path });
        }
    }
});

// Editor plugin that consumes the contract
export const editorPlugin = plugin({
    id: 'editor',
    name: 'Editor',
    version: '1.0.0',

    start(api) {
        api.register('code-editor', {
            type: 'panel',
            component: CodeEditor,
            label: 'Editor',
            contracts: {
                accepts: ['file-selection']
            }
        });

        // Subscribe to file selection
        api.subscribe('file-selected', ({ path }) => {
            console.log('File selected:', path);
        });

        // Find all file browsers
        const browsers = api.findByContract({ provides: 'file-browser' });
        console.log('Found browsers:', browsers.length);
    }
});

Released under the MIT License.