# @zseven-w/pen-react
React UI SDK for [OpenPencil](https://github.com/ZSeven-W/openpencil) — a complete set of hooks, components, and panels to build a design editor with React.
## Install
```bash
npm install @zseven-w/pen-react
# or
bun add @zseven-w/pen-react
```
**Peer dependencies:** `react@^19`, `react-dom@^19`, `@radix-ui/react-*` (popover, select, separator, slider, switch, toggle, tooltip)
## Overview
`pen-react` wraps [`@zseven-w/pen-engine`](../pen-engine) into idiomatic React: a context provider, 10 semantic hooks, and 39 ready-to-use components covering the full editor UI.
```
```
## Quick Start
```tsx
import {
DesignProvider,
DesignCanvas,
CoreToolbar,
LayerPanel,
PropertyPanel,
} from '@zseven-w/pen-react';
function Editor() {
return (
);
}
```
## Hooks
All hooks subscribe to the engine and re-render on change:
```tsx
import {
useDesignEngine,
useDocument,
useSelection,
useViewport,
useActiveTool,
useHistory,
useActiveNode,
useActivePage,
useHover,
useVariables,
} from '@zseven-w/pen-react';
function Inspector() {
const node = useActiveNode(); // PenNode | null
const selection = useSelection(); // string[]
const { canUndo, undo } = useHistory();
const viewport = useViewport(); // { zoom, panX, panY }
const tool = useActiveTool(); // ToolType
const doc = useDocument(); // PenDocument
const page = useActivePage(); // PenPage
const hoverId = useHover(); // string | null
const variables = useVariables(); // VariableDefinition[]
const engine = useDesignEngine(); // DesignEngine (escape hatch)
return
Selected: {selection.length} nodes
;
}
```
## Provider
### Uncontrolled mode
Engine owns the document. Good for standalone editors:
```tsx
{children}
```
### Controlled mode
Parent owns the document. Good for integration into existing state:
```tsx
setDoc(newDoc)}>
{children}
```
Echo-loop prevention is built in — `onDocumentChange` won't fire for changes that originated from the parent.
## Components
### Canvas
| Component | Description |
| -------------- | ----------------------------------------------------------------------------------------- |
| `DesignCanvas` | GPU-rendered canvas with CanvasKit/Skia. Handles zoom, pan, resize, and all interactions. |
```tsx
console.log('Canvas ready')}
loadingFallback={}
/>
```
### Toolbar
| Component | Description |
| ------------------- | ----------------------------------------------------------------- |
| `CoreToolbar` | Main tool selection bar (select, frame, shapes, text, pen, image) |
| `ToolButton` | Individual tool button with icon + active state |
| `ShapeToolDropdown` | Dropdown for shape tools (rectangle, ellipse, polygon, line) |
| `BooleanToolbar` | Union, subtract, intersect, exclude operations |
### Panels
| Component | Description |
| ------------------ | ----------------------------------------------------- |
| `LayerPanel` | Hierarchical tree view with drag-and-drop reordering |
| `LayerItem` | Single layer row — collapse, visibility, lock, rename |
| `LayerContextMenu` | Right-click menu: copy, paste, delete, group, z-order |
| `PropertyPanel` | Tabbed property inspector for the selected node |
| `PageTabs` | Multi-page tab bar with add/rename/reorder/delete |
| `StatusBar` | Bottom bar with zoom, coordinates, node count |
### Property Sections
Drop these into your own property panel or use `PropertyPanel` which includes all of them:
| Section | Edits |
| ---------------------- | ----------------------------------------------- |
| `SizeSection` | x, y, width, height, rotation, constraints |
| `FillSection` | Solid color, linear/radial gradient |
| `StrokeSection` | Color, thickness, dash pattern, cap, join |
| `TextSection` | Font family, size, weight, color, alignment |
| `TextLayoutSection` | Line height, letter spacing, paragraph spacing |
| `CornerRadiusSection` | Uniform or per-corner border radius |
| `EffectsSection` | Drop shadow, inner shadow, blur |
| `LayoutSection` | Auto-layout direction, gap, justify, align |
| `LayoutPaddingSection` | Uniform or per-side padding |
| `AppearanceSection` | Opacity, blend mode |
| `IconSection` | Icon name picker (Lucide icons) |
| `ImageSection` | Image source, fit mode |
| `ExportSection` | Code generation target (React, HTML, Vue, etc.) |
### Shared UI
| Component | Description |
| ------------------ | --------------------------------------------------- |
| `ColorPicker` | Color input with swatch palette and hex input |
| `NumberInput` | Numeric field with drag-to-adjust and arrow keys |
| `SectionHeader` | Collapsible section header with title + actions |
| `FontPicker` | Font family selector with preview |
| `VariablePicker` | Design variable reference picker (`$primary`, etc.) |
| `IconPickerDialog` | Modal icon browser with search and categories |
## UI Store
Ephemeral UI state (panel open/close, drag state) managed by Zustand — separate from engine state:
```tsx
import { useUIStore } from '@zseven-w/pen-react';
const { layerPanelOpen, toggleLayerPanel } = useUIStore();
```
## Styling
Components use [Tailwind CSS](https://tailwindcss.com/) + [CVA](https://cva.style/docs) for variant styling, and [Radix UI](https://www.radix-ui.com/) primitives for accessibility. Override styles via `className` props or Tailwind's design token system.
## License
[MIT](./LICENSE)