Skip to main content

Workspace

<Workspace /> is the React component that hosts the Fabric canvas inside the Ydesign editor — the area where users actually draw and interact with the design. Its responsibilities are intentionally narrow:

  • On mount, spin up an @ydesign/core Editor bound to the store
  • Insert the Fabric canvas into the container and watch for size changes
  • Render the transparent-background checkerboard and the inner shadow
  • On unmount, destroy the Editor and fully clean up Fabric's events and DOM

All business behavior (zoom, selection, layers, crop, import/export, …) is exposed through the Store API and the @ydesign/core handlers, not through this component's props.


Basic usage

import { createStore } from '@ydesign/react-editor';
import Workspace from '@ydesign/react-editor/canvas/workspace';

const store = createStore({
key: 'YOUR_API_KEY',
token: 'user-token',
});

export default function App() {
return (
<div style={{ height: '100vh' }}>
<Workspace store={store} />
</div>
);
}

<Workspace /> automatically stretches to fill its parent. You don't need to set its size manually — just make sure the parent has an explicit width / height (or flex: 1).

⚠️ Workspace mounts its Fabric canvas at id="canvas", so there can be only one <Workspace /> instance per page today. For "multi-canvas comparison" scenarios, swap via routing or conditional rendering.


Initial configuration

Workspace pulls its initial size and workarea defaults from store:

const store = createStore({ key: 'YOUR_API_KEY' });

store.width; // => 1080 (default)
store.height; // => 1080 (default)
store.backgroundColor; // => '#fff'
store.unit; // => 'px'
store.dpi; // => 72

To change the defaults before Workspace mounts, call the corresponding actions right after createStore:

const store = createStore({ key: 'YOUR_API_KEY' });
store.setSize({ width: 1200, height: 800 });
store.setUnit({ unit: 'mm', dpi: 300 });
store.setBackgroundColor('#f0f0f0');

// ...then mount <Workspace store={store} />

Resize the canvas

store.setSize({ width: 1600, height: 900 });

// Same thing, directly on the handler
store.editor?.workareaHandler.setSize({ width: 1600, height: 900 });

A resize triggers:

  1. workareaHandler rebuilds the workarea rect
  2. zoomHandler.auto() fits the canvas to screen
  3. A workarea:changed event syncs store.width / store.height
  4. A history entry is pushed (so the resize is undoable)

Unit conversion

store.setUnit({ unit: 'mm', dpi: 300 });
// 1080px @ 72dpi → 381mm @ 72dpi → 4500px @ 300dpi

See Store overview · canvas size / unit / background.


Change the background

// Solid
store.setBackgroundColor('#f5f5f5');

// Transparent (a checkerboard is shown automatically)
store.setBackgroundColor('rgba(255, 255, 255, 0)');
store.setBackgroundColor('transparent');

// Linear gradient
store.setBackgroundColor({
type: 'linear',
coords: { x1: 0, y1: 0, x2: 1, y2: 1 },
colorStops: [
{ offset: 0, color: '#ff6a00' },
{ offset: 1, color: '#2253eb' },
],
});

When the background color has alpha (rgba(..., <1) or transparent), Workspace automatically renders a checkerboard beneath the workarea as a visual hint:

┌─────────────────────────┐
│ ▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦ │
│ ▦▦▦▦▦ workarea ▦▦▦▦▦▦▦▦ │
│ ▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦▦ │
└─────────────────────────┘

Zoom & viewport

Zoom is handled by ZoomHandler, not by Workspace props:

const zoom = store.editor?.zoomHandler;

zoom?.zoomIn(); // one step in
zoom?.zoomOut(); // one step out
zoom?.zoomToOne(); // 100%
zoom?.zoomToFit(); // fit to screen
zoom?.zoomToRatio(1.5); // 150%
zoom?.centerViewport(); // reset pan

// Current zoom is mirrored on store
console.log(store.scale);

Ydesign ships a <ZoomButtons /> component (bottom-right). If you want custom zoom UI, just call the handler methods above.


Rulers · bleed · guidelines

Rulers

store.rulesVisible = true;

Bleed (for print)

const workarea = store.editor?.workareaHandler;

workarea?.setBleed?.(3); // 3mm bleed
store.bleedVisible = true; // show red bleed lines

Smart guidelines / snapping

Enabled by default, managed by GuidelinesHandler:

store.editor?.guidelinesHandler.enable();
store.editor?.guidelinesHandler.disable();

Dragging an object shows center / equidistant / alignment guides automatically.


Keyboard shortcuts

Workspace ships a built-in set of shortcuts (registered in @ydesign/core's HotkeyHandler):

KeyAction
Ctrl/⌘ + ZUndo
Ctrl/⌘ + Shift + ZRedo
Ctrl/⌘ + C / V / XCopy / paste / cut
Ctrl/⌘ + DDuplicate
Ctrl/⌘ + ASelect all
Ctrl/⌘ + G / Shift + GGroup / ungroup
Delete / BackspaceDelete selection
ArrowNudge by 1px
Shift + ArrowNudge by 10px
Ctrl/⌘ + = / - / 0Zoom in / out / reset

Custom shortcuts

Register your own via HotkeyHandler.bind:

store.editor?.hotkeyHandler.bind({
'ctrl+s, command+s': e => {
e.preventDefault();
saveToServer();
},
'ctrl+shift+d, command+shift+d': () => {
store.editor?.objectsHandler.duplicate();
},
});

Your bindings are added on top of the built-in ones. To truly replace a default behavior, call the corresponding handler method inside your callback.


Context menu

A canvas context menu (<ContextMenu />) is coming soon — see ContextMenu (planned).

Until it ships, if you need a right-click menu today, listen for contextmenu on the container, call event.preventDefault(), and render your own menu whose items call store.editor.xxxHandler. A full example lives in the context-menu doc's "Workarounds" section.


Container styling

Workspace's root element has a stable id and default light/dark background:

<div id="canvas_container" className="bg-[#ecf0f1] dark:bg-[#92969d]">
{/* inner canvas */}
</div>

Override the background via global CSS:

#canvas_container {
background: #1e1e1e !important;
}

#canvas_container .inside-shadow {
box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.15);
}

To change the Tailwind class itself, override with [data-theme="dark"] or wrap Workspace in your own shell.


Unmount & cleanup

On unmount, <Workspace /> takes care of:

  1. Removing the after:render listener from the Fabric canvas
  2. Calling editor.destroy() to tear down all handlers and DOM

So just let React unmount the component normally (via routing or {show && <Workspace />}). No manual cleanup is required.

When Workspace is mounted through createDesignEditorApp, unmount via the React root:

const { root } = createDesignEditorApp({ container, key: 'YOUR_API_KEY' });

// Unmount
root.unmount();

Debugging: access the underlying Editor

For convenience during development, Workspace attaches the Editor instance to window._c:

// Browser console
window._c.zoomHandler.zoomToFit();
window._c.objectsHandler.clear();
window._c.historyHandler.getStatus();

⚠️ Development-only backdoor. Don't rely on window._c in production code — use store.editor.xxxHandler instead.


Differences from Polotno's Workspace

If you've used <Workspace /> from Polotno, keep these differences in mind:

FeaturePolotnoYdesign
components.PageControls (per-page controls)❌ (no Page concept — see Scenes)
components.NoPages (empty state UI)❌ (single-canvas model doesn't need it)
backgroundColor / pageBorderColor / paddingX / paddingY props❌ (use CSS or store.setBackgroundColor)
onKeyDown custom key handlerUse store.editor.hotkeyHandler.bind({ ... }) instead
setTransformerStyle / setHighlighterStyle (Konva)❌ (Fabric-based; configure via Fabric selection styles)
Canvas context menu🚧 planned (see ContextMenu)
Auto checkerboard for transparent background✅ built-in

The underlying engine is different: Polotno uses Konva, Ydesign uses Fabric.js v6. Their coordinate systems and event models differ — APIs don't map 1:1.


Next