@micahgoodman/whiteboard
v1.0.0
Published
A heterogeneous drag-and-drop whiteboard component for React with multi-module support
Downloads
5
Maintainers
Readme
@micahgoodman/whiteboard
A heterogeneous drag-and-drop whiteboard component for React with multi-module support. Display multiple types of content (notes, tasks, bookmarks, etc.) on a single canvas.
Features
✨ Heterogeneous Support - Mix different module types on one whiteboard
🎯 Drag & Drop - Smooth, native mouse-based dragging
💾 Position Persistence - Automatic localStorage saving
🎨 Customizable - Full control over styling and behavior
📱 Type Safe - Full TypeScript support
🔌 Zero Dependencies - Only requires React
Installation
npm install @micahgoodman/whiteboard
# or
yarn add @micahgoodman/whiteboardPeer Dependencies
{
"react": "^18.0.0"
}Quick Start
Single Module Example
import { Whiteboard, WhiteboardItem, WhiteboardComponentMap } from '@micahgoodman/whiteboard';
// Define your item type
type Note = WhiteboardItem & {
type: 'note';
text: string;
};
// Create your card component
const NoteCard = ({ item }: { item: WhiteboardItem }) => {
const note = item as Note;
return <div>{note.text}</div>;
};
// Create your detail component
const NoteDetail = ({ item, onClose }: { item: WhiteboardItem; onClose: () => void }) => {
const note = item as Note;
return (
<div>
<h2>Note Details</h2>
<p>{note.text}</p>
<button onClick={onClose}>Close</button>
</div>
);
};
// Register components
const componentMap: WhiteboardComponentMap = {
note: {
CardComponent: NoteCard,
DetailComponent: NoteDetail,
},
};
// Use the whiteboard
function App() {
const [notes, setNotes] = useState<Note[]>([
{ id: '1', type: 'note', text: 'Hello World!' },
]);
const [selectedId, setSelectedId] = useState<string | null>(null);
return (
<Whiteboard
items={notes}
selectedId={selectedId}
onSelect={setSelectedId}
config={{
cardWidth: 320,
cardHeight: 200,
cardBorderRadius: 12,
spacing: 40,
localStorageKey: 'my-whiteboard-positions',
}}
componentMap={componentMap}
toolbarConfig={{
showCount: true,
addButtonLabel: '+ New Note',
}}
onAddNew={() => {
const newNote: Note = {
id: Date.now().toString(),
type: 'note',
text: 'New note',
};
setNotes([...notes, newNote]);
}}
/>
);
}Multi-Module Example
import { Whiteboard, WhiteboardItem, WhiteboardComponentMap } from '@micahgoodman/whiteboard';
type Note = WhiteboardItem & { type: 'note'; text: string };
type Task = WhiteboardItem & { type: 'task'; title: string; done: boolean };
type Bookmark = WhiteboardItem & { type: 'bookmark'; url: string; title: string };
const componentMap: WhiteboardComponentMap = {
note: {
CardComponent: NoteCard,
DetailComponent: NoteDetail,
},
task: {
CardComponent: TaskCard,
DetailComponent: TaskDetail,
},
bookmark: {
CardComponent: BookmarkCard,
DetailComponent: BookmarkDetail,
},
};
function App() {
const allItems: WhiteboardItem[] = [
...notes.map(n => ({ ...n, type: 'note' as const })),
...tasks.map(t => ({ ...t, type: 'task' as const })),
...bookmarks.map(b => ({ ...b, type: 'bookmark' as const })),
];
return (
<Whiteboard
items={allItems}
selectedId={selectedId}
onSelect={setSelectedId}
config={whiteboardConfig}
componentMap={componentMap}
toolbarConfig={{
showCount: true,
customActions: (
<>
<button onClick={addNote}>+ Note</button>
<button onClick={addTask}>+ Task</button>
<button onClick={addBookmark}>+ Bookmark</button>
</>
),
}}
/>
);
}API
<Whiteboard>
Main whiteboard component.
Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| items | WhiteboardItem[] | ✅ | Array of items to display |
| selectedId | string \| null | ✅ | ID of currently selected item |
| onSelect | (id: string) => void | ✅ | Called when item is selected |
| config | WhiteboardConfig | ✅ | Whiteboard configuration |
| componentMap | WhiteboardComponentMap | ✅ | Component registry |
| onAddNew | () => void | ❌ | Called when add button clicked |
| emptyStateConfig | EmptyStateConfig | ❌ | Empty state customization |
| toolbarConfig | ToolbarConfig | ❌ | Toolbar customization |
Types
WhiteboardItem
interface WhiteboardItem {
id: string;
type: string; // Module discriminator
}WhiteboardConfig
type WhiteboardConfig = {
cardWidth: number;
cardHeight: number;
cardBorderRadius: number;
spacing: number;
localStorageKey: string;
};WhiteboardComponentMap
type WhiteboardComponentMap = {
[moduleType: string]: {
CardComponent: React.ComponentType<{
item: WhiteboardItem;
isSelected: boolean;
onClick: () => void;
onCardClick: (e: React.MouseEvent) => void;
}>;
DetailComponent: React.ComponentType<{
item: WhiteboardItem;
onClose: () => void;
}>;
};
};Lower-Level Components
The package also exports lower-level draggable components:
<DraggableCanvas>
Container for draggable items with optional grid.
<DraggableCanvas showGrid gridSize={20}>
{/* Draggable items */}
</DraggableCanvas><DraggableCard>
Individual draggable item wrapper.
<DraggableCard
id="card-1"
position={{ x: 100, y: 100 }}
onPositionChange={(id, pos) => console.log(id, pos)}
width={300}
>
<YourContent />
</DraggableCard>Styling
The whiteboard comes with minimal default styling. For custom styling:
- Override inline styles via
toolbarConfigand component props - Use CSS classes - components accept
classNameprops - Global CSS - Target
.btn.primaryand other classes
TypeScript
Full TypeScript support with exported types:
import type {
WhiteboardItem,
WhiteboardConfig,
WhiteboardComponentMap,
WhiteboardProps,
Position,
} from '@micahgoodman/whiteboard';Browser Support
- Modern browsers (Chrome, Firefox, Safari, Edge)
- Requires
localStoragefor position persistence
License
MIT
Contributing
Issues and pull requests welcome!
