Rich, web-friendly editor UI framework for SolidJS + TypeScript
solid-stage docs quickstart.md
4.8 kB · 157 lines
Markdown
at main

Quick start #

Install #

pnpm add @dolphinspired/solid-stage

Peer dependency: Solid 1.9+. The library is browser-only (it uses ResizeObserver and getBoundingClientRect; there is no SSR build).

Vite: exclude Kobalte from dep optimization #

Add this to your vite.config.ts:

optimizeDeps: { exclude: ['@kobalte/core'] },

Without it, the dev server can throw React is not defined when a Kobalte-based popup first opens, such as the ColorField picker. vite-plugin-solid resolves Kobalte to raw JSX, but it only sets up your direct Solid dependencies, and Kobalte reaches you through this library. So Vite's dep optimizer compiles that JSX with its React default. Production builds aren't affected, so vite build won't warn you.

Clear the cache after adding it, by deleting node_modules/.vite or running Vite once with --force.

Import the styles once #

The library ships one stylesheet. Import it once, anywhere in your app entry:

import '@dolphinspired/solid-stage/styles.css';

Wrap your app in a .stage-root #

Every token (colour, size, spacing) is declared on .stage-root, and all portalled UI (select popovers, the modal, tooltips) mounts into the nearest .stage-root. So your stage must live inside an element carrying that class:

function App() {
  return (
    <div class="stage-root">
      {/* …your stage… */}
    </div>
  );
}

To restyle, set CSS variables on that element (or any container inside it):

<div class="stage-root" style={{ '--stage-color-accent': '#ff4488' }}>

A minimal stage #

EditorPanels is the outer shell: optional bars, optional left/right panels, and a required center (your canvas, preview, or any content). A root stage owns the window (position: fixed; inset: 0).

import { EditorPanels, Section, TextField } from '@dolphinspired/solid-stage';
import { createSignal } from 'solid-js';
import '@dolphinspired/solid-stage/styles.css';

function App() {
  const [name, setName] = createSignal('Untitled');

  return (
    <div class="stage-root">
      <EditorPanels
        center={<div style={{ display: 'grid', 'place-items': 'center' }}>Your world here</div>}
        leftPanel={
          <Section title="Settings">
            <TextField name="Name" value={name} onChange={setName} />
          </Section>
        }
      />
    </div>
  );
}

That's a complete editor: a centre region, a collapsible left panel, and a collapsible section (whose open/closed state already persists to localStorage) holding one text field.

Add a reactive store #

Hand-wiring a createSignal per field gets old. createEditorStore gives you one typed reactive source of truth with optional persistence, and a bind helper that fills in value + onChange for any field:

import { EditorPanels, FieldGroup, Section, TextField, NumberField, BooleanField, createEditorStore } from '@dolphinspired/solid-stage';

interface Settings {
  name: string;
  level: number;
  snap: boolean;
}

function App() {
  const [state, setState, bind] = createEditorStore<Settings>({
    defaults: { name: 'Untitled', level: 1, snap: true },
    persist: { key: 'my-editor', version: 1 },
  });

  return (
    <div class="stage-root">
      <EditorPanels
        center={<Viewport settings={state} />}
        leftPanel={
          <Section title="Settings">
            <FieldGroup>
              <TextField name="Name" {...bind('name')} />
              <NumberField name="Level" {...bind('level')} min={1} />
              <BooleanField name="Snap to grid" {...bind('snap')} />
            </FieldGroup>
          </Section>
        }
      />
    </div>
  );
}

{...bind('name')} expands to value={() => state.name} onChange={(v) => setState('name', v)}. The store reads/writes through state/setState (Solid's createStore API), so your canvas reacts with a plain createEffect. See State.

Add a canvas #

For a 3D/2D world in the centre, CanvasStage gives you a sized viewport with World / HUD / Pointer layers:

import { CanvasStage } from '@dolphinspired/solid-stage';

<CanvasStage onResize={({ width, height }) => renderer.setSize(width, height)}>
  <CanvasStage.World mountRef={(el) => el.appendChild(renderer.domElement)} />
  <CanvasStage.Hud>{/* overlay UI */}</CanvasStage.Hud>
</CanvasStage>

Drop it into EditorPanels's center. See Layout.

Where next #

  • Concepts — the mental model behind all of this.
  • Controls & fields — the full widget set.
  • Overlays — wire up modal / toast / tooltip (each needs a one-time host component mounted in your .stage-root).