BoardKitExamples
React Whiteboard Example
Complete React whiteboard with canvas, toolbar, collaboration, and all features
A complete React whiteboard application using all BoardKit hooks and components.
App.tsx
import { BoardKitProvider } from '@hfu.digital/boardkit-react';
import { Whiteboard } from './Whiteboard';
const BOARD_ID = 'demo-board';
export function App() {
return (
<BoardKitProvider
config={{
apiUrl: 'http://localhost:3000',
wsUrl: 'ws://localhost:3000/board',
authToken: localStorage.getItem('token') ?? undefined,
theme: {
selectionColor: '#3b82f6',
handleColor: '#1d4ed8',
cursorLabelFont: '12px sans-serif',
},
}}
>
<Whiteboard boardId={BOARD_ID} />
</BoardKitProvider>
);
}Whiteboard.tsx
import {
BoardCanvas,
Toolbar,
PageNavigator,
ParticipantList,
CursorOverlay,
SelectionHandles,
Minimap,
DropZone,
useBoard,
useCollaboration,
useTool,
useViewport,
useHistory,
usePresence,
useGrid,
useExport,
useImageImport,
useErrorRecovery,
useKeyboardShortcuts,
} from '@hfu.digital/boardkit-react';
export function Whiteboard({ boardId }: { boardId: string }) {
const { board, loading, error } = useBoard(boardId);
const { connectionState } = useCollaboration(boardId);
const { activeTool, setTool } = useTool();
const { viewport, zoomTo, fitToContent, resetZoom } = useViewport();
const { undo, redo, canUndo, canRedo } = useHistory();
const { participants } = usePresence();
const { gridConfig, toggleGrid, toggleSnap } = useGrid();
const { exportPng, exportPdf, isExporting } = useExport(boardId);
const { isDragging } = useImageImport(boardId);
const { lastError, clearError } = useErrorRecovery();
useKeyboardShortcuts();
if (loading) {
return <div style={styles.loading}>Loading board...</div>;
}
if (error) {
return <div style={styles.error}>Error: {error}</div>;
}
return (
<div style={styles.container}>
{/* Top bar */}
<header style={styles.header}>
<h1 style={styles.title}>{board?.name}</h1>
<div style={styles.headerActions}>
<button onClick={undo} disabled={!canUndo}>Undo</button>
<button onClick={redo} disabled={!canRedo}>Redo</button>
<span style={styles.separator} />
<button onClick={toggleGrid}>
Grid: {gridConfig.enabled ? 'On' : 'Off'}
</button>
<button onClick={toggleSnap}>
Snap: {gridConfig.snapEnabled ? 'On' : 'Off'}
</button>
<span style={styles.separator} />
<button onClick={() => zoomTo(viewport.zoom - 0.1)}>-</button>
<span>{Math.round(viewport.zoom * 100)}%</span>
<button onClick={() => zoomTo(viewport.zoom + 0.1)}>+</button>
<button onClick={fitToContent}>Fit</button>
<button onClick={resetZoom}>Reset</button>
</div>
<div style={styles.headerRight}>
<ParticipantList />
<span style={styles.connectionDot(connectionState)} />
</div>
</header>
{/* Toolbar (left side) */}
<Toolbar activeTool={activeTool} onToolChange={setTool} />
{/* Canvas area */}
<main style={styles.canvas}>
<BoardCanvas />
<CursorOverlay />
<SelectionHandles />
</main>
{/* Bottom bar */}
<footer style={styles.footer}>
<PageNavigator />
<div style={styles.footerRight}>
<button onClick={exportPng} disabled={isExporting}>PNG</button>
<button onClick={exportPdf} disabled={isExporting}>PDF</button>
</div>
</footer>
{/* Minimap (bottom-right corner) */}
<Minimap width={200} height={150} />
{/* Drop zone overlay */}
<DropZone boardId={boardId} />
{/* Error banner */}
{lastError && (
<div style={styles.errorBanner}>
<span>{lastError.message}</span>
{lastError.recoverable && (
<button onClick={clearError}>Dismiss</button>
)}
</div>
)}
</div>
);
}
const styles = {
container: { position: 'relative', width: '100vw', height: '100vh', overflow: 'hidden' } as const,
loading: { display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh' } as const,
error: { color: 'red', padding: 20 } as const,
header: { display: 'flex', alignItems: 'center', padding: '8px 16px', borderBottom: '1px solid #e5e7eb', gap: 16 } as const,
title: { fontSize: 16, fontWeight: 600, margin: 0 } as const,
headerActions: { display: 'flex', alignItems: 'center', gap: 8 } as const,
headerRight: { marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 } as const,
separator: { width: 1, height: 20, backgroundColor: '#e5e7eb' } as const,
canvas: { flex: 1, position: 'relative' } as const,
footer: { display: 'flex', alignItems: 'center', padding: '4px 16px', borderTop: '1px solid #e5e7eb' } as const,
footerRight: { marginLeft: 'auto', display: 'flex', gap: 8 } as const,
errorBanner: { position: 'fixed', bottom: 20, left: '50%', transform: 'translateX(-50%)', backgroundColor: '#fef2f2', border: '1px solid #fca5a5', borderRadius: 8, padding: '8px 16px', display: 'flex', gap: 8, alignItems: 'center' } as const,
connectionDot: (state: string) => ({
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: state === 'connected' ? '#22c55e' : state === 'connecting' ? '#eab308' : '#ef4444',
}) as const,
};Features Demonstrated
- Board loading with error handling
- Realtime collaboration with connection status
- All 9 drawing tools
- Undo/redo
- Zoom controls (zoom in/out, fit to content, reset)
- Grid and snap controls
- PNG and PDF export
- Image drag-and-drop
- Page navigation
- Participant avatars with colors
- Remote cursor overlay
- Selection handles
- Minimap
- Error banner with dismiss
- Keyboard shortcuts (via
useKeyboardShortcuts)