On this page

@miosa/react v0.2.0 provides four React components for embedding MIOSA sandboxes in any React application.

Install

npm install @miosa/react
# xterm peer deps  -  required only if you use MiosaTerminal
npm install @xterm/xterm @xterm/addon-fit

Add the bundled stylesheet to your app entry point:

import '@miosa/react/styles.css';

MiosaThemeProvider

Wrap your component tree with MiosaThemeProvider to set a default theme for all nested Miosa components.

import { MiosaThemeProvider } from '@miosa/react';

export default function App() {
  return (
    <MiosaThemeProvider theme="dark">

      {/* all Miosa components inherit theme="dark" */}
    </MiosaThemeProvider>

  );
}

Props

PropTypeDefaultDescription
theme"dark" \| "light""dark"Visual theme applied to all child components
injectStylesbooleanfalseWhen true, lazily injects the stylesheet via a <link> tag. Prefer the explicit import '@miosa/react/styles.css' instead.
childrenReact.ReactNode-Required. Your component tree.

MiosaPreview

Renders a sandboxed <iframe> showing the HTTP preview URL for a running sandbox.

import { MiosaPreview } from '@miosa/react';

// Using a short-lived preview token (recommended for public-facing embeds)

<MiosaPreview
  sandboxId="sb_abc123"
  previewToken={tokenFromYourBackend}
/>

// Using an API key (server-rendered / trusted context only)

<MiosaPreview
  sandboxId="sb_abc123"
  apiKey={process.env.MIOSA_API_KEY}
/>

Props

PropTypeDefaultDescription
sandboxIdstring-Required. The sandbox ID to preview.
previewTokenstring-Short-lived preview token minted by your backend. Mutually exclusive with apiKey.
apiKeystring-Your MIOSA API key. Mutually exclusive with previewToken. Do not expose in the browser.
theme"dark" \| "light""dark"Visual theme.
classNamestring-CSS class forwarded to the root element.
onError(err: Error) => void-Called when the preview URL cannot be resolved.

Exactly one of previewToken or apiKey must be provided.

States

The component progresses through idle → loading → ready (or error). A skeleton placeholder is shown while loading. On error, an accessible role="alert" message is rendered.


MiosaTerminal

Renders a full PTY terminal connected to a sandbox over WebSocket, powered by xterm.js. The terminal auto-fits to its container and reconnects on resize.

import { MiosaTerminal } from '@miosa/react';

<MiosaTerminal
  sandboxId="sb_abc123"
  apiKey={process.env.MIOSA_API_KEY}
  onResize={(cols, rows) => console.log('resized to', cols, rows)}
  onError={(err) => console.error(err)}
/>

Props

PropTypeDefaultDescription
sandboxIdstring-Required. The sandbox to connect to.
apiKeystring-Required. Your MIOSA API key.
theme"dark" \| "light""dark"Sets the xterm.js color theme.
classNamestring-CSS class forwarded to the root <div>.
onResize(cols: number, rows: number) => void-Called whenever the terminal dimensions change.
onError(err: Error) => void-Called on WebSocket errors or if xterm fails to load.

Peer dependencies

@xterm/xterm and @xterm/addon-fit are loaded dynamically at runtime (lazy import), so they will not bloat your initial bundle. The component falls back to the legacy xterm package name if @xterm/xterm is unavailable.


MiosaFileTree

Renders an interactive file browser backed by the sandbox filesystem. Directories expand and collapse; clicking a file fires onSelect. The tree subscribes to filesystem change events and refreshes automatically.

import { MiosaFileTree } from '@miosa/react';
import type { FileNode } from '@miosa/react';

<MiosaFileTree
  sandboxId="sb_abc123"
  apiKey={process.env.MIOSA_API_KEY}
  showHidden={false}
  defaultExpanded={['/workspace/src']}
  onSelect={(file: FileNode) => openFile(file.path)}
  onChange={(tree: FileNode[]) => console.log('tree updated', tree)}
/>

Props

PropTypeDefaultDescription
sandboxIdstring-Required.
apiKeystring-Required.
theme"dark" \| "light""dark"Visual theme.
classNamestring-CSS class on the root <nav>.
showHiddenbooleanfalseWhen true, dotfiles and hidden directories are included.
defaultExpandedstring[][]Array of directory paths to start expanded, e.g. ['/workspace/src'].
onSelect(file: FileNode) => void-Called when a file (not a directory) is clicked.
onChange(tree: FileNode[]) => void-Called after each tree refresh with the full updated tree.
onError(err: Error) => void-Called on load or watch errors.

FileNode type

interface FileNode {
  path: string;       // absolute path, e.g. "/workspace/src/index.ts"
  name: string;       // filename segment, e.g. "index.ts"
  type: 'file' | 'directory';
  children?: FileNode[]; // present for directories
}

MiosaUsage

Renders a usage summary for a specific end-user - compute seconds, aggregated over a time period. Intended for billing dashboards in white-label products.

import { MiosaUsage } from '@miosa/react';

<MiosaUsage
  externalUserId="user_xyz"
  apiKey={process.env.MIOSA_API_KEY}
  period="30d"
/>

Props

PropTypeDefaultDescription
externalUserIdstring-Required. Your platform’s user ID (the external_user_id used when creating sandboxes).
apiKeystring-Required.
period"7d" \| "30d" \| "90d""30d"Time window for usage aggregation.
theme"dark" \| "light""dark"Visual theme.
classNamestring-CSS class on the root element.
onError(err: Error) => void-Called on fetch errors.

The component renders a total usage count and a bar chart of daily buckets.


Theming and styling

All components attach BEM class names to their root element for CSS targeting:

ComponentRoot class
MiosaPreview.miosa-preview
MiosaTerminal.miosa-terminal
MiosaFileTree.miosa-file-tree
MiosaUsage.miosa-usage

Theme variants are appended as modifier classes: .miosa-preview--dark, .miosa-preview--light, etc.

The data-miosa-theme attribute is also set on the root element, allowing CSS attribute selectors:

[data-miosa-theme="dark"] .miosa-preview__frame {
  border: 1px solid #333;
}

End-to-end example: Next.js

This example creates a sandbox on page load, then shows the preview, terminal, and file tree side by side.

app/api/sandbox-token/route.ts - backend token endpoint (never expose your API key to the client)

import { NextResponse } from 'next/server';
import Miosa from '@miosa/sdk';

const client = new Miosa({ apiKey: process.env.MIOSA_API_KEY! });

export async function POST(req: Request) {
  const { sandboxId } = await req.json() as { sandboxId: string };
  const sandbox = await client.sandboxes.get(sandboxId);
  // @ts-expect-error - previewToken is available on the sandbox object
  const { token } = await sandbox.previewToken(3600);
  return NextResponse.json({ token });
}

app/workbench/page.tsx - client component

'use client';

import { useEffect, useState } from 'react';
import {
  MiosaPreview,
  MiosaTerminal,
  MiosaFileTree,
  MiosaThemeProvider,
} from '@miosa/react';
import type { FileNode } from '@miosa/react';
import '@miosa/react/styles.css';

const SANDBOX_ID = 'sb_abc123'; // created elsewhere in your app

export default function WorkbenchPage() {
  const [token, setToken] = useState<string | null>(null);
  const [selectedFile, setSelectedFile] = useState<string | null>(null);

  useEffect(() => {
    fetch('/api/sandbox-token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sandboxId: SANDBOX_ID }),
    })
      .then((r) => r.json() as Promise<{ token: string }>)
      .then(({ token }) => setToken(token));
  }, []);

  if (!token) return <div>Loading workspace…</div>;

  return (
    <MiosaThemeProvider theme="dark">

      <div style={{ display: 'grid', gridTemplateColumns: '240px 1fr 1fr', height: '100vh' }}>
        {/* File tree sidebar */}
        <MiosaFileTree
          sandboxId={SANDBOX_ID}
          apiKey={/* server-component prop or env  -  omit on public pages */}
          onSelect={(file: FileNode) => setSelectedFile(file.path)}
        />

        {/* Preview panel */}
        <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
          {selectedFile && (
            <div style={{ padding: '8px', fontSize: 12, color: '#888' }}>
              {selectedFile}
            </div>
          )}
          <MiosaPreview
            sandboxId={SANDBOX_ID}
            previewToken={token}
            style={{ flex: 1 }}
          />

        </div>

        {/* Terminal panel */}
        <MiosaTerminal
          sandboxId={SANDBOX_ID}
          apiKey={process.env.NEXT_PUBLIC_MIOSA_API_KEY!}
          onError={(err) => console.error('[terminal]', err)}
        />
      </div>
    </MiosaThemeProvider>

  );
}

See also

Was this helpful?