npm package: @superatomai/renderer-react
Description: SuperAtom Runtime SDK - React components for rendering JSON UI schemas
Latest version: 0.0.2

# @superatomai/renderer-react

React components for rendering JSON UI schemas from SuperAtom responses.

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Available Components](#available-components)
- [Dynamic Component Loading from Bundles](#dynamic-component-loading-from-bundles)
- [JSON Schema Structure](#json-schema-structure)
- [Advanced Usage](#advanced-usage)
- [TypeScript Support](#typescript-support)
- [Development](#development)

## Installation

```bash
pnpm add @superatomai/renderer-react
# or
npm install @superatomai/renderer-react
# or
yarn add @superatomai/renderer-react
```

### Peer Dependencies

This package requires React 18+:

```bash
pnpm add react react-dom
```

## Quick Start

### 1. Basic Usage

```tsx
import { useState } from 'react';
import { DSLRenderer } from '@superatomai/renderer-react';

function DashboardPanel() {
  const [uiSchema, setUiSchema] = useState(null);
  const [uiData, setUiData] = useState(null);

  const handleRenderUI = (schema: any, data: any) => {
    setUiSchema(schema);
    setUiData(data || {});
  };

  return (
    <div>
      {uiSchema && (
        <DSLRenderer
          dsl={uiSchema}
          data={uiData}
          handlers={{}}
        />
      )}
    </div>
  );
}

export default DashboardPanel;
```

### 2. Report Generation

```tsx
import { useState } from 'react';
import { DSLRenderer } from '@superatomai/renderer-react';

function ReportGenerator() {
  const [reports, setReports] = useState([]);

  const addReport = (schema: any, data: any) => {
    setReports(prev => [...prev, {
      id: Date.now(),
      schema,
      data: data || {}
    }]);
  };

  return (
    <div className="report-container">
      {reports.map(report => (
        <div key={report.id} className="report-card">
          <DSLRenderer
            dsl={report.schema}
            data={report.data}
          />
        </div>
      ))}
    </div>
  );
}
```

### 3. Dashboard with Multiple Panels

```tsx
import { useState } from 'react';
import { DSLRenderer } from '@superatomai/renderer-react';

interface DashboardPanel {
  id: string;
  title: string;
  schema: any;
  data: any;
}

function Dashboard() {
  const [panels, setPanels] = useState<DashboardPanel[]>([]);

  const addPanel = (title: string, schema: any, data: any) => {
    setPanels(prev => [...prev, {
      id: `panel-${Date.now()}`,
      title,
      schema,
      data
    }]);
  };

  return (
    <div className="grid grid-cols-2 gap-4">
      {panels.map(panel => (
        <div key={panel.id} className="panel-card">
          <h3>{panel.title}</h3>
          <DSLRenderer
            dsl={panel.schema}
            data={panel.data}
          />
        </div>
      ))}
    </div>
  );
}
```

## API Reference

### `<DSLRenderer>`

Main component for rendering JSON UI schemas.

#### Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `dsl` | `UIComponent` | Yes | The UI schema to render |
| `data` | `Record<string, any>` | No | Data context for the UI |
| `context` | `Record<string, any>` | No | Additional context variables |
| `handlers` | `Record<string, Function>` | No | Event handlers for components |
| `onNavigate` | `(uiid: string, params?: Record<string, any>) => void` | No | Navigation handler |

### Component Registry

#### `COMP_REGISTRY`

Global registry containing all available components (both native and dynamically loaded).

```typescript
import { COMP_REGISTRY } from '@superatomai/renderer-react';

// Access the registry
console.log(Object.keys(COMP_REGISTRY)); // ['COMP_ECHART', 'COMP_AGGRID', ...]
```

#### `getComponentStates()`

Returns the current state of all components.

```typescript
import { getComponentStates } from '@superatomai/renderer-react';

const states = getComponentStates();
// Returns: { loaded: string[], loading: string[], failed: string[] }
```

#### `resetFailedComponent(componentId: string)`

Resets the failed state of a specific component, allowing it to be retried.

```typescript
import { resetFailedComponent } from '@superatomai/renderer-react';

resetFailedComponent('echarts'); // Allows echarts to be loaded again
```

#### `resetAllFailedComponents()`

Resets all failed components.

```typescript
import { resetAllFailedComponents } from '@superatomai/renderer-react';

const count = resetAllFailedComponents(); // Returns number of components reset
```

#### `<DynamicComponent>`

Low-level component for rendering registry components directly (rarely needed).

```typescript
import { DynamicComponent } from '@superatomai/renderer-react';

<DynamicComponent
  type="COMP_ECHART"
  props={{ option: {...} }}
/>
```

#### Example

```tsx
<DSLRenderer
  dsl={{
    id: 'chart-component',
    name: 'ChartComponent',
    data: { chartData: [...] },
    render: {
      id: 'render-root',
      type: 'COMP_ECHART',
      props: {
        option: { $bind: 'chartData' }
      }
    }
  }}
  data={{ chartData: [...] }}
  handlers={{
    onChartClick: (params) => console.log('Chart clicked', params)
  }}
/>
```

## Available Components

The SDK includes the following native components:

- **COMP_ECHART** - ECharts for data visualization
- **COMP_AGGRID** - AG Grid for data tables
- **COMP_HANDSONTABLE** - Handsontable for spreadsheets
- **COMP_LEAFLET** - Leaflet for maps
- **COMP_MAPBOX** - Mapbox GL for advanced maps
- **COMP_VIS_NETWORK** - Network/graph visualizations
- **COMP_THREE_SCENE** - 3D visualizations with Three.js
- **COMP_PDF_VIEWER** - PDF document viewer
- **COMP_LUCKYSHEET** - Excel-like spreadsheet
- **COMP_MARKDOWN** - Markdown renderer
- **COMP_ICONIFY_ICON** - Icon library

## Dynamic Component Loading from Bundles

This package supports automatic registration of custom components from external bundles. This allows you to build your own components separately, bundle them, and have them automatically loaded into the component registry at runtime.

### How It Works

1. **Package Initialization**: When you import `@superatomai/renderer-react`, it automatically:
   - Sets up an event listener for component loading
   - Exposes `window.runtime.componentsLoaded()` for bundles to call
   - Watches `window.SUPERATOM.COMPONENTS` for new components

2. **Bundle Setup**: Your bundle calls `setup()` from `@superatomai/sdk-web` and triggers the event
3. **Auto-Registration**: Components are wrapped and registered in `COMP_REGISTRY` with keys like `COMP_YourComponent`

### Creating a Bundle with Custom Components

**Step 1: Define Your Components**

```typescript
// src/components/DynamicBarChart.tsx
export default function DynamicBarChart({ data, config }) {
  return (
    <div>
      {/* Your custom bar chart implementation */}
    </div>
  );
}
```

**Step 2: Setup Bundle Entry Point**

```typescript
// src/superatom.ts
import { createRoot } from 'react-dom/client';
import { createElement } from 'react';
import { setup } from '@superatomai/sdk-web';
import DynamicBarChart from './components/DynamicBarChart';
import DynamicDataTable from './components/DynamicDataTable';

const COMPONENTS = {
  DynamicBarChart,
  DynamicDataTable,
  // Add your other components...

  // REQUIRED: mount function for React version isolation
  mount: (Component, container, props) => {
    // Use the bundle's own React instance to create a root
    const root = createRoot(container);
    root.render(createElement(Component, props));
    return { unmount: () => root.unmount() };
  }
};

const SUPERATOM = {
  init: async () => {
    // Register components with setup()
    setup(COMPONENTS);

    // Trigger component registration event
    (window as any).runtime?.componentsLoaded();

    // Send metadata to SDK if needed
    const sdkClient = (window as any).SA_WEB_SDK_CLIENT;
    if (sdkClient) {
      sdkClient.sendComponents(componentsMetadata);
    }
  },
  destroy: () => {
    // Cleanup if needed
  }
};

export default SUPERATOM;
```

> **Important**: The `mount` function is required for React version isolation. It ensures your bundle's React instance is used to render components, preventing version conflicts with the host application.

**Step 3: Build Your Bundle**

Use your preferred bundler (Vite, Webpack, etc.) to create a UMD or IIFE bundle that exposes `SUPERATOM` globally.

### Using Bundles in Your Application

**Option 1: Load Bundle Script Dynamically**

```tsx
import { useEffect, useState } from 'react';
import { DSLRenderer } from '@superatomai/renderer-react';

function App() {
  const [bundleLoaded, setBundleLoaded] = useState(false);

  useEffect(() => {
    // Load bundle script
    const script = document.createElement('script');
    script.src = 'https://your-cdn.com/bundle.js';
    script.onload = () => {
      // Initialize bundle after package is ready
      if (window.SUPERATOM?.init) {
        window.SUPERATOM.init().then(() => {
          setBundleLoaded(true);
        });
      }
    };
    document.head.appendChild(script);
  }, []);

  return (
    <div>
      {bundleLoaded ? (
        <DSLRenderer
          dsl={{
            id: 'custom-chart',
            render: {
              id: 'chart-root',
              type: 'COMP_DynamicBarChart', // Your custom component
              props: {
                data: { $bind: 'chartData' }
              }
            }
          }}
          data={{ chartData: [...] }}
        />
      ) : (
        <div>Loading components...</div>
      )}
    </div>
  );
}
```

**Option 2: Static Script Tag**

```html
<!DOCTYPE html>
<html>
<head>
  <!-- Load your bundle -->
  <script src="/path/to/bundle.js"></script>
</head>
<body>
  <div id="root"></div>

  <script type="module">
    import { createRoot } from 'react-dom/client';
    import App from './App';

    // Initialize bundle components
    window.SUPERATOM.init().then(() => {
      createRoot(document.getElementById('root')).render(<App />);
    });
  </script>
</body>
</html>
```

### Important Considerations

⚠️ **Loading Order**: The npm package must be imported BEFORE calling `window.SUPERATOM.init()`, otherwise the event listener won't be set up yet.

✅ **Best Practice Pattern**:
```typescript
// 1. Import package (sets up event listeners)
import { DSLRenderer } from '@superatomai/renderer-react';

// 2. Load bundle script
// 3. Call SUPERATOM.init() after both are ready
// 4. Render components
```

### Accessing Bundle Components in DSL

Once your bundle is loaded and initialized, components are automatically registered with the `COMP_` prefix:

```json
{
  "id": "my-ui",
  "render": {
    "id": "root",
    "type": "COMP_DynamicBarChart",
    "props": {
      "data": { "$bind": "salesData" },
      "config": {
        "title": "Sales by Region"
      }
    }
  }
}
```

### Debugging Bundle Components

```typescript
import { getComponentStates } from '@superatomai/renderer-react';

// Check component loading status
const states = getComponentStates();
console.log('Loaded components:', states.loaded);
console.log('Loading components:', states.loading);
console.log('Failed components:', states.failed);

// Access the registry directly (for debugging)
console.log('Available components:', window.COMP_REGISTRY);
```

### React Version Isolation

Bundle components are automatically wrapped with `BundleComponentWrapper` to ensure React version isolation. This means:

- Your bundle can use its own version of React
- The wrapper ensures proper rendering in the host application's React tree
- Each component has isolated error boundaries

## JSON Schema Structure

### UIComponent Schema

```typescript
{
  id: string;              // Unique component ID
  name?: string;           // Component name
  props?: Record<string, any>;  // Component props
  states?: Record<string, any>; // Component state
  data?: Record<string, any>;   // Component data
  render: UIElement;       // Root UI element to render
}
```

### UIElement Schema

```typescript
{
  id: string;              // Unique element ID
  type: string;            // Element type (div, span, COMP_*)
  props?: Record<string, any>;  // Element props
  children?: UIElement[] | string;  // Child elements

  // Conditional rendering
  if?: { $exp: string };
  else?: UIElement;

  // Loop rendering
  for?: {
    in: { $bind: string } | { $exp: string };
    as: string;
    key?: string;
    index?: string;
  };

  // Data binding
  // Use { $bind: 'data.path' } for data binding
  // Use { $exp: 'expression' } for expressions
}
```

### Example: Complete UI Schema

```json
{
  "id": "sales-dashboard",
  "name": "SalesDashboard",
  "data": {
    "salesData": [
      { "region": "North", "sales": 1000 },
      { "region": "South", "sales": 1500 }
    ]
  },
  "render": {
    "id": "root",
    "type": "div",
    "props": {
      "style": { "padding": "20px" }
    },
    "children": [
      {
        "id": "title",
        "type": "h1",
        "children": "Sales Dashboard"
      },
      {
        "id": "chart",
        "type": "COMP_ECHART",
        "props": {
          "option": {
            "xAxis": {
              "type": "category",
              "data": { "$exp": "salesData.map(d => d.region)" }
            },
            "yAxis": { "type": "value" },
            "series": [{
              "type": "bar",
              "data": { "$exp": "salesData.map(d => d.sales)" }
            }]
          },
          "style": { "height": "400px" }
        }
      }
    ]
  }
}
```

## Advanced Usage

### Custom Event Handlers

```tsx
<DSLRenderer
  dsl={schema}
  data={data}
  handlers={{
    onRowClick: (row) => {
      console.log('Row clicked:', row);
      // Handle row click
    },
    onFilterChange: (filters) => {
      console.log('Filters changed:', filters);
      // Update filters
    }
  }}
/>
```

### Navigation Between UIs

```tsx
<DSLRenderer
  dsl={schema}
  data={data}
  onNavigate={(uiid, params) => {
    console.log('Navigate to:', uiid, 'with params:', params);
    // Load new UI based on uiid
    loadUI(uiid, params);
  }}
/>
```

### Error Handling

```tsx
import { DSLErrorBoundary } from '@superatomai/renderer-react';

function App() {
  return (
    <DSLErrorBoundary>
      <DSLRenderer dsl={schema} data={data} />
    </DSLErrorBoundary>
  );
}
```

### Component State Management

```tsx
import { getComponentStates, resetFailedComponent } from '@superatomai/renderer-react';

// Check which components are loaded
const states = getComponentStates();
console.log('Loaded:', states.loaded);
console.log('Failed:', states.failed);

// Reset a failed component to retry loading
resetFailedComponent('echarts');
```

## TypeScript Support

Full TypeScript support with type definitions:

```tsx
import type {
  UIComponent,
  UIElement,
  DSLRendererProps
} from '@superatomai/renderer-react';

const schema: UIComponent = {
  id: 'my-component',
  render: {
    id: 'root',
    type: 'div',
    children: 'Hello World'
  }
};
```

## Development

```bash
# Install dependencies
pnpm install

# Build the package
pnpm build

# Watch mode for development
pnpm dev

# Type checking
pnpm typecheck
```

## License

MIT

## Support

For issues and questions:
- GitHub Issues: [superatom/sdk-runtime](https://github.com/superatom/sdk-runtime)
- Documentation: [docs.superatom.ai](https://docs.superatom.ai)