Integration

Custom UI and Cursors

Hide built-in UI sections, drive tools from custom controls, and override multiplayer cursor rendering.

Custom UI and Cursors

Telva lets you keep the rendering/editor core while replacing parts of the host UI.

Hide built-in panels and render your own toolbar

import * as React from 'react'
import { TVShapeType, TVToolType, Telva, TelvaApp } from 'telva'

const AppContext = React.createContext<TelvaApp>({} as TelvaApp)

function useApp() {
  return React.useContext(AppContext)
}

function ToolButton({ type, children }: React.PropsWithChildren<{ type: TVToolType }>) {
  const app = useApp()
  const isActive = app.useStore((state) => state.appState.activeTool === type)

  return (
    <button onClick={() => app.selectTool(type)} aria-pressed={isActive}>
      {children}
    </button>
  )
}

export default function CustomUI() {
  const [app, setApp] = React.useState<TelvaApp>()

  return (
    <div style={{ position: 'relative', width: '100%', height: 620 }}>
      <Telva
        onMount={setApp}
        showUI
        showStyles={false}
        showTools={false}
        showPages={false}
        showMenu={false}
      />

      {app && (
        <AppContext.Provider value={app}>
          <div style={{ position: 'absolute', bottom: 16, left: 16, display: 'flex', gap: 8 }}>
            <ToolButton type="select">Select</ToolButton>
            <ToolButton type="erase">Erase</ToolButton>
            <ToolButton type={TVShapeType.Sticky}>Sticky</ToolButton>
          </div>
        </AppContext.Provider>
      )}
    </div>
  )
}

Override multiplayer cursor rendering

import * as React from 'react'
import { TVUserStatus, Telva, TelvaApp } from 'telva'
import { CursorComponent } from 'telva-core'

const CustomCursor: CursorComponent<{ name: string }> = ({ color, metadata }) => (
  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
    <div style={{ width: 12, height: 12, borderRadius: '100%', background: color }} />
    <div style={{ background: 'white', padding: '4px 8px', borderRadius: 4 }}>
      {metadata?.name}
    </div>
  </div>
)

export default function CustomCursors() {
  function onMount(app: TelvaApp) {
    app.updateUsers([
      {
        id: 'fakeuser1',
        point: [100, 100],
        color: 'orange',
        status: TVUserStatus.Connected,
        activeShapes: [],
        selectedIds: [],
        metadata: { name: 'Steve' },
      },
    ])
  }

  return <Telva onMount={onMount} components={{ Cursor: CustomCursor }} />
}

Register custom React components for canvas placement

Pass a ReactComponentEntry[] to reactComponents:

import type { ReactComponentEntry } from 'telva'
import Grainient from './Grainient'

const REACT_COMPONENTS: ReactComponentEntry[] = [
  {
    id: 'grainient',
    name: 'Grainient',
    description: 'Animated gradient shader background',
    defaultSize: [400, 300],
    sourcePath: 'apps/www/react/Grainient.tsx',
    previewColor: '#7B2FFF',
    component: Grainient,
  },
]

<Telva reactComponents={REACT_COMPONENTS} />

On this page