@clipmind/mindmap
v1.0.7
Published
A React-based mind map component with multiple layout strategies
Maintainers
Readme
@clipmind/mindmap
A React-based mind map component with multiple layout strategies, theming support, and rich node features.
Installation
npm install @clipmind/mindmap
# or
yarn add @clipmind/mindmap
# or
pnpm add @clipmind/mindmapPeer Dependencies
Make sure you have the following peer dependencies installed:
npm install react react-dom mobx mobx-react styled-components slate slate-react slate-historyQuick Start
import { useEffect, useState } from 'react';
import { MindMapEditor, generateStore, loadFromMarkdown } from '@clipmind/mindmap';
import styled from 'styled-components';
import type { MindMapStore } from '@clipmind/mindmap';
const Container = styled.div`
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
`;
export function MindmapDemo() {
const [store, setStore] = useState<MindMapStore | null>(null);
useEffect(() => {
// generateStore is async to ensure fonts are loaded before layout calculation
generateStore().then(s => {
loadFromMarkdown('# Hello World\n\n## Item 1\n### Item 1.1\n## Item 2', s);
setStore(s);
} );
}, []);
if (!store) {
return (
<Container>{'Loading...'}</Container>
);
}
return (
<Container>
<MindMapEditor store={store} />
</Container>
);
}Features
- 6 Layout Types: MindMap, Logic, Organization, Timeline, Tree, and Fishbone
- 50+ Color Themes: Light and dark themes with customization support
- Rich Nodes: Text, images, markers, checkboxes, and boundaries
- Export: SVG, PNG, JPG, and PDF export capabilities
- Undo/Redo: Full history management
- Markdown Import: Load mind maps from markdown text
- Serialization: Save and restore mind map state
Store Initialization
Use generateStore to create a properly initialized MindMapStore. This function is async to ensure fonts are fully loaded before layout calculations, preventing text truncation issues.
import { generateStore } from '@clipmind/mindmap';
// Basic usage
const store = await generateStore();
// With custom font
const store = await generateStore({ fontFamily: 'Inter' });GenerateStoreOptions
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| fontFamily | string | 'Lexend' | Google Font to use for node text |
Supported Fonts
The following Google Fonts are supported:
Lexend, Roboto, Open Sans, Lato, Montserrat, Oswald, Source Sans Pro, Raleway, PT Sans, Merriweather, Inter, Roboto Mono, Fira Code, JetBrains Mono, Poppins, Quicksand, Playfair Display, Lora, Pacifico, Dancing Script
Data Import from Markdown
You can import mind map data from markdown text using the loadFromMarkdown function.
Fetching Markdown from ClipMind API
Use the ClipMind API to generate markdown from any text input.
Get your API Token: Visit API Docs to obtain your API token.
curl -X POST https://api.clipmind.com/public/mindmaps/markdown \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"text": "My MindMap\n- Idea 1\n- Idea 2",
"options": {
"mode": "summarize",
"language": "english",
"length": "medium"
}
}'Loading Markdown into Store
import { loadFromMarkdown, generateStore } from '@clipmind/mindmap';
// Create store (async to ensure fonts are loaded)
const store = await generateStore();
// Fetch markdown from ClipMind API
const response = await fetch('https://api.clipmind.com/public/mindmaps/markdown', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${YOUR_API_TOKEN}`,
},
body: JSON.stringify({
text: 'React 入门指南',
options: {
mode: 'summarize',
language: 'chinese',
length: 'medium',
},
}),
});
const { data } = await response.json();
// Load markdown into store
loadFromMarkdown(data, store);Manual Markdown Input
You can also load markdown directly without using the API:
const markdown = `# React 入门指南
## 核心概念
- JSX 语法
- 组件化开发
- 函数组件
- 类组件
## Hooks
- useState
- useEffect
- useContext`;
loadFromMarkdown(markdown, store);Markdown Format
The parser supports the following markdown syntax:
# Central Topic
## Branch 1
- Item 1.1
- Item 1.2
-- Sub-item 1.2.1
## Branch 2
- Item 2.1Heading levels:
#→ Root node (level 1)##→ First-level branches (level 2)###→ Second-level branches (level 3)
List items:
-→ Child node--→ Grandchild node---→ Great-grandchild node- Indentation also affects level:
- Item(2 spaces = deeper level)
Images in Markdown
Include images with optional dimensions:
# My Mind Map
## {100x80} Brand
-  Feature 1Supported dimension formats:
{100x80}- width x height{width=100 height=80}- explicit attributes{width=100}- width only{100}- width only (shorthand)
Metadata Preservation
Export with convertMindMapToMarkdownWithMetadata preserves node styles:
# Central Topic
## Branch 1
<!-- clipmind-metadata
{"version":"cm-md/1","nodes":[{"shape":"rounded"},{"markers":["star"]}]}
-->Serialization / Deserialization
Save and restore mind map state for persistence:
// Save to localStorage
const data = await store.serialize();
localStorage.setItem('mindmap', JSON.stringify(data));
// Restore from localStorage
const saved = localStorage.getItem('mindmap');
if (saved) {
store.deserialize(JSON.parse(saved));
}IMindMapData Structure
interface IMindMapData {
id: string;
rootNode: IMindMapNode;
theme: string; // Color theme ID
layout: {
type: ILayoutType; // 'mindmap' | 'logic' | 'org' | 'timeline' | 'tree' | 'fishbone'
theme: string; // Layout theme ID
};
resource?: IMindMapResource; // Images and other resources
createdAt?: string;
updatedAt?: string;
}Node Operations
Adding Nodes
// Add a child node
store.addChildNode({
title: 'New Child',
parentId: 'root', // Parent node ID
enableScrollToNode: true, // Auto-scroll to new node
});
// Add a sibling node (same level as specified node)
store.addSiblingNode('node-id');
// Get the ID of the last added child
const newNodeId = store.getLastChildNodeId('parent-id');Deleting Nodes
// Delete single node
store.deleteNodes(['node-id']);
// Delete multiple nodes
store.deleteNodes(['node-1', 'node-2', 'node-3']);
// Note: Root node ('root') cannot be deletedModifying Nodes
// Set node text
store.setNodeText('node-id', 'New Title');
// Set node text without saving to history
store.setNodeText('node-id', 'New Title', false);
// Update node image
store.updateNodeImage('node-id', {
src: 'https://example.com/image.png',
alt: 'Description',
box: { x: 0, y: 0, w: 100, h: 80 }
});Node Selection
// Select a node
store.setSelectedNodes(['node-id']);
// Get selected nodes
const selected = store.selectedNodes;
// Clear selection
store.setSelectedNodes([]);Node Features
Markers
Add icon markers to nodes:
// Available markers: 'star', 'heart', 'flag', 'check', 'cross', 'question', etc.
store.addMarker('node-id', 'star');
store.removeMarker('node-id', 'star');Checkboxes
Add todo checkboxes to nodes:
store.setNodeCheckbox('node-id', {
checked: false,
shape: 'square' // 'square' | 'circle' | 'diamond'
});
// Toggle checkbox
store.toggleNodeCheckbox('node-id');Boundaries
Add visual boundaries around node groups:
store.setNodeBoundary('node-id', {
enabled: true,
style: 'rounded', // 'rounded' | 'square' | 'wave'
color: '#ff6b35'
});Custom Styles
Apply custom styles to individual nodes:
store.setNodeCustomStyle('node-id', {
backgroundColor: '#ff6b35',
textColor: '#ffffff',
fontSize: 16,
fontWeight: 'bold'
});Undo / Redo
// Check if operations are available
const canUndo = store.canUndo();
const canRedo = store.canRedo();
// Perform undo/redo
store.undo();
store.redo();Image Export
SVG Export
store.saveSvg({
isCopy: false, // Copy to clipboard instead of download
exportCheckbox: true, // Include checkbox visuals
sealConfig: { // Optional watermark
style: 'corner',
text: 'My Brand'
}
});PNG Export
store.savePng({
scale: 2, // Export scale (1-4)
transparentBackground: false, // Transparent or themed background
isCopy: false, // Copy to clipboard
exportCheckbox: true,
targetWidth: 1920, // Optional fixed width
targetHeight: 1080 // Optional fixed height
});JPG Export
store.saveJpg({
scale: 2,
exportCheckbox: true,
targetWidth: 1920,
targetHeight: 1080
});PDF Export
store.savePdf({
scale: 2,
transparentBackground: false,
exportCheckbox: true
});Preview Generation
// Get PNG as data URL (for thumbnails, previews)
const dataUrl = await store.savePngPreview({
scale: 1,
transparentBackground: false
});Layout Types
// Set layout type and theme
store.setLayout('mindmap', 'mindmap-underline');
store.setLayout('logic', 'logic-minimal');
store.setLayout('org', 'org-corporate');
store.setLayout('timeline', 'timeline-default');
store.setLayout('tree', 'tree-trunk');
store.setLayout('fishbone', 'fishbone-classic');Available Layout Themes
| Layout Type | Available Themes |
|-------------|------------------|
| mindmap | mindmap-underline, mindmap-modern, mindmap-organic, mindmap-creative |
| logic | logic-underline, logic-underline-stack, logic-minimal, logic-minimal-circle, logic-modern |
| org | org-corporate, org-modern |
| timeline | timeline-default, timeline-axis, timeline-horizontal |
| tree | tree-trunk, tree-structure |
| fishbone | fishbone-classic, fishbone-modern |
Color Themes
Light Themes
import {
irisTheme,
neonTheme,
amethystTheme,
sunsetTheme,
oceanTheme,
forestTheme,
cherryBlossomTheme,
volcanoTheme,
hermesOrangeTheme,
tiffanyBlueTheme,
rainbowTheme,
premiumBlackGrayTheme,
roseGoldTheme,
deepSeaTheme,
lavenderTheme,
champagneTheme,
midnightBlueTheme,
emeraldTheme,
chinaRedTheme,
frenchFlagTheme,
germanFlagTheme,
americanFlagTheme,
japaneseFlagTheme,
roseTheme,
slateTheme,
goldTheme,
cyanTheme,
indigoTheme,
} from '@clipmind/mindmap';Dark Themes
import {
darkIrisTheme,
darkNeonTheme,
darkAmethystTheme,
darkSunsetTheme,
darkOceanTheme,
darkForestTheme,
darkCherryBlossomTheme,
darkVolcanoTheme,
darkHermesOrangeTheme,
darkTiffanyBlueTheme,
darkRainbowTheme,
darkPremiumBlackGrayTheme,
darkRoseGoldTheme,
darkDeepSeaTheme,
darkLavenderTheme,
darkChampagneTheme,
darkMidnightBlueTheme,
darkEmeraldTheme,
darkChinaRedTheme,
darkFrenchFlagTheme,
darkGermanFlagTheme,
darkAmericanFlagTheme,
darkJapaneseFlagTheme,
darkRoseTheme,
darkSlateTheme,
darkGoldTheme,
darkCyanTheme,
darkIndigoTheme,
} from '@clipmind/mindmap';Theme Utilities
import { AVAILABLE_THEMES, LIGHT_THEMES, DARK_THEMES } from '@clipmind/mindmap';
// Get all themes
console.log(AVAILABLE_THEMES.length); // 56 themes
// Apply a theme
store.colorTheme = darkNeonTheme;
// Reset node colors after theme change
store.resetNodeThemes();Canvas Operations
// Fit mind map to screen
store.canvas.fitToScreen();
// Zoom controls
store.canvas.zoomIn();
store.canvas.zoomOut();
store.canvas.setScale(1.5);
// Get current view state
const view = store.canvas.getView();
// { x: number, y: number, scale: number }Complete Example: ClipMind API Integration
Get your API Token: Visit API Docs to obtain your API token.
import { useState, useEffect } from 'react';
import {
MindMapStore,
MindMapEditor,
generateStore,
loadFromMarkdown,
} from '@clipmind/mindmap';
// Get your token from https://clipmind.tech/workspace
const API_TOKEN = 'your-api-token';
function MindMapDemo() {
const [store, setStore] = useState<MindMapStore | null>(null);
useEffect(() => {
// generateStore is async to ensure fonts are loaded before layout calculation
generateStore().then(setStore);
}, []);
const handleGenerateFromAPI = async () => {
if (!store) return;
const response = await fetch('https://api.clipmind.com/public/mindmaps/markdown', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_TOKEN}`,
},
body: JSON.stringify({
text: 'React 入门指南',
options: {
mode: 'brainstorm',
language: 'chinese',
length: 'medium',
},
}),
});
const { data } = await response.json();
loadFromMarkdown(data, store);
};
const handleExport = () => {
store?.savePng({ scale: 2 });
};
const handleSave = async () => {
if (!store) return;
const data = await store.serialize();
localStorage.setItem('mindmap', JSON.stringify(data));
};
const handleLoad = () => {
const saved = localStorage.getItem('mindmap');
if (saved && store) {
store.deserialize(JSON.parse(saved));
}
};
if (!store) {
return <div>Loading...</div>;
}
return (
<div style={{ width: '100vw', height: '100vh' }}>
<MindMapEditor store={store} />
<div style={{ display: 'flex', justifyContent: 'center', position: 'absolute', top: 80, left: '50%', transform: 'translateX(-50%)', gap: 16 }}>
<button onClick={handleGenerateFromAPI}>Generate from API</button>
<button onClick={handleExport}>Export PNG</button>
<button onClick={handleSave}>Save</button>
<button onClick={handleLoad}>Load</button>
</div>
</div>
);
}