A reusable React component for exploring and viewing markdown files with file tree navigation
bash
npm install @asafarim/markdown-explorer-viewer
or
yarn add @asafarim/markdown-explorer-viewer
or
pnpm add @asafarim/markdown-explorer-viewer
`
$3
`bash
npm install react react-dom react-router-dom
`
🚀 Quick Start
$3
`tsx
import { MarkdownExplorer } from '@asafarim/markdown-explorer-viewer';
import { parseFileTree } from '@asafarim/markdown-explorer-viewer';
// Create a virtual file tree from your markdown content
const files = {
'/docs/README.md': '# Welcome\n\nThis is the main documentation.',
'/docs/getting-started.md': '# Getting Started\n\nFollow these steps...',
'/docs/api/overview.md': '# API Overview\n\nOur API provides...',
'/docs/api/reference.md': '# API Reference\n\nComplete API documentation.'
};
const fileTree = parseFileTree(files);
function App() {
return (
fileTree={fileTree}
theme="dark"
showBreadcrumbs={true}
enableSearch={true}
initialRoute="/docs/README.md"
/>
);
}
`
$3
`tsx
import { MarkdownExplorer } from '@asafarim/markdown-explorer-viewer';
import { useNavigate, useLocation } from 'react-router-dom';
function DocumentationPage() {
const navigate = useNavigate();
const location = useLocation();
const handleNavigate = (path: string) => {
navigate(/docs${path});
};
return (
fileTree={docsFileTree}
initialRoute={location.pathname.replace('/docs', '')}
onNavigate={handleNavigate}
theme="auto"
showFileTree={true}
enableSearch={true}
/>
);
}
`
$3
`tsx
import { FileNode } from '@asafarim/markdown-explorer-viewer';
const customFileTree: FileNode = {
name: 'Documentation',
path: '/',
type: 'folder',
children: [
{
name: 'Introduction',
path: '/intro',
type: 'folder',
children: [
{
name: 'Overview.md',
path: '/intro/overview.md',
type: 'file',
content: '# Overview\n\nWelcome to our documentation...'
}
]
},
{
name: 'Guide.md',
path: '/guide.md',
type: 'file',
content: '# User Guide\n\nThis guide will help you...'
}
]
};
`
📚 API Reference
$3
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| fileTree | FileNode | undefined | Virtual file tree structure |
| rootPath | string | '/' | Root path for navigation |
| theme | 'light' \| 'dark' \| 'auto' | 'auto' | Color theme |
| className | string | '' | Additional CSS class |
| initialRoute | string | undefined | Initial path to navigate to |
| onNavigate | (path: string, node: FileNode) => void | undefined | Navigation callback |
| enableSearch | boolean | true | Enable file search |
| searchPlaceholder | string | 'Search files...' | Search input placeholder |
| showIcons | boolean | true | Show file/folder icons |
| showFileTree | boolean | true | Show file tree sidebar |
| renderFileIcon | (node: FileNode) => ReactNode | undefined | Custom file icon renderer |
| renderFolderIcon | (node: FileNode) => ReactNode | undefined | Custom folder icon renderer |
| sidebarWidth | string | '280px' | Sidebar width |
| showBreadcrumbs | boolean | true | Show breadcrumb navigation |
| markdownComponents | Record | undefined | Custom markdown components |
$3
`typescript
interface FileNode {
name: string; // Display name
path: string; // Full path
type: 'file' | 'folder'; // Node type
children?: FileNode[]; // Child nodes (for folders)
content?: string; // File content (for files)
lastModified?: string; // Last modification date
size?: number; // File size in bytes
}
`
$3
#### parseFileTree(files: Record
Converts a flat file structure into a hierarchical tree.
`tsx
const files = {
'/docs/README.md': '# Documentation',
'/docs/guide/setup.md': '# Setup Guide',
'/docs/guide/usage.md': '# Usage Examples'
};
const tree = parseFileTree(files);
`
#### findNodeByPath(tree: FileNode, path: string): FileNode | null
Finds a specific node in the tree by its path.
`tsx
const node = findNodeByPath(fileTree, '/docs/README.md');
`
#### searchNodes(tree: FileNode, query: string): FileNode[]
Searches for nodes matching the query.
`tsx
const results = searchNodes(fileTree, 'api');
`
🎨 Theming & Customization
$3
The component uses CSS custom properties for theming:
`css
:root {
--me-primary: #2563eb;
--me-primary-hover: #1d4ed8;
--me-text-primary: #1f2937;
--me-text-secondary: #6b7280;
--me-bg-primary: #ffffff;
--me-bg-secondary: #f9fafb;
--me-border: #e5e7eb;
--me-radius: 0.375rem;
--me-sidebar-width: 280px;
}
[data-theme="dark"] {
--me-text-primary: #f9fafb;
--me-text-secondary: #d1d5db;
--me-bg-primary: #111827;
--me-bg-secondary: #1f2937;
--me-border: #374151;
}
`
$3
`tsx
className="my-custom-explorer"
fileTree={fileTree}
theme="dark"
/>
`
`css
.my-custom-explorer {
border: 2px solid #2563eb;
border-radius: 8px;
}
.my-custom-explorer .file-tree {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
`
$3
`tsx
const customComponents = {
h1: ({ children }) => (
{children}
),
code: ({ children, className }) => (
{children}
)
};
fileTree={fileTree}
markdownComponents={customComponents}
/>
`
🔧 Advanced Usage
$3
`tsx
// Example with file system API
async function loadFileSystem() {
const files: Record = {};
// Load files from your source
files['/README.md'] = await fetch('/docs/README.md').then(r => r.text());
files['/guide.md'] = await fetch('/docs/guide.md').then(r => r.text());
return parseFileTree(files);
}
function App() {
const [fileTree, setFileTree] = useState(null);
useEffect(() => {
loadFileSystem().then(setFileTree);
}, []);
if (!fileTree) return Loading...;
return ;
}
`
$3
`tsx
function DynamicMarkdownExplorer() {
const [currentContent, setCurrentContent] = useState('');
const handleNavigate = async (path: string, node: FileNode) => {
if (node.type === 'file' && !node.content) {
// Load content dynamically
const content = await fetch(/api/files${path}).then(r => r.text());
node.content = content;
setCurrentContent(content);
}
};
return (
fileTree={fileTree}
onNavigate={handleNavigate}
/>
);
}
`
$3
`tsx
function SearchableExplorer() {
const [searchResults, setSearchResults] = useState([]);
const handleSearch = (query: string) => {
if (query.trim()) {
const results = searchNodes(fileTree, query);
setSearchResults(results);
} else {
setSearchResults([]);
}
};
return (
type="text"
placeholder="Search documentation..."
onChange={(e) => handleSearch(e.target.value)}
/>
{searchResults.length > 0 && (
Search Results:
{searchResults.map(node => (
{node.name}
))}
)}
fileTree={fileTree}
enableSearch={true}
/>
);
}
`
🌍 Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
🤝 Contributing
Contributions are welcome! Please read our Contributing Guide for details.
📄 License
MIT © Ali Safari
🔗 Related Packages
- @asafarim/project-card - Project showcase cards
- @asafarim/display-code - Syntax-highlighted code blocks
- @asafarim/paginated-project-grid` - Project grid with pagination