UNPKG

@asafarim/markdown-explorer-viewer

Version:

A reusable React component for exploring and viewing markdown files with file tree navigation

1,589 lines (1,270 loc) 43.7 kB
import { parseFileTree } from '../../src/utils/fileUtils'; // Sample markdown files for the demo export const sampleFiles: Record<string, string> = { '/README.md': `# Markdown Explorer Viewer Demo Welcome to the **Markdown Explorer Viewer** demo! This interactive demonstration showcases the powerful features of our markdown viewing and navigation component. ## 🌟 Features - 📁 **File Tree Navigation** - Browse through folders and files - 📝 **Markdown Rendering** - Rich markdown support with syntax highlighting - 🔍 **Search Functionality** - Find files quickly with built-in search - 🎨 **Theme Support** - Toggle between light and dark themes - 📱 **Responsive Design** - Works perfectly on mobile devices ## 🚀 Getting Started Navigate through the file tree on the left to explore different markdown documents. You can: 1. Click on folders to expand/collapse them 2. Click on files to view their content 3. Use the search box to find specific files 4. Toggle the theme to see dark mode 5. Use breadcrumbs for quick navigation Enjoy exploring!`, '/docs/introduction.md': `# Introduction Welcome to our comprehensive documentation! This guide will help you understand and use all the features available in the Markdown Explorer Viewer. ## What is Markdown Explorer Viewer? The Markdown Explorer Viewer is a React component that provides: - **File tree navigation** for organizing documentation - **Rich markdown rendering** with GitHub Flavored Markdown support - **Search capabilities** to find content quickly - **Responsive design** that works on all devices - **Theme support** for both light and dark modes ## Key Benefits ### For Developers - Easy integration with existing React applications - TypeScript support for better development experience - Customizable theming and styling options - Performance optimized with lazy loading ### For Users - Intuitive navigation similar to VS Code - Fast search across all documentation - Clean, readable markdown rendering - Accessible design with keyboard navigation ## Next Steps Check out the [Getting Started](/docs/getting-started.md) guide to learn how to implement this in your project!`, '/docs/getting-started.md': `# Getting Started This guide will walk you through setting up and using the Markdown Explorer Viewer in your React application. ## Installation \`\`\`bash npm install @asafarim/markdown-explorer-viewer # or yarn add @asafarim/markdown-explorer-viewer # or pnpm add @asafarim/markdown-explorer-viewer \`\`\` ## Basic Usage Here's a simple example to get you started: \`\`\`tsx import { MarkdownExplorer, parseFileTree } from '@asafarim/markdown-explorer-viewer'; // Your markdown files const files = { '/README.md': '# Welcome\\n\\nThis is your documentation.', '/guide.md': '# Guide\\n\\nStep-by-step instructions.' }; // Convert to file tree const fileTree = parseFileTree(files); function App() { return ( <div style={{ height: '100vh' }}> <MarkdownExplorer fileTree={fileTree} theme="auto" enableSearch={true} /> </div> ); } \`\`\` ## Advanced Configuration ### Custom Themes \`\`\`tsx <MarkdownExplorer fileTree={fileTree} theme="dark" className="my-custom-explorer" /> \`\`\` ### Navigation Callbacks \`\`\`tsx const handleNavigate = (path: string, node: FileNode) => { console.log('Navigated to:', path); // Update URL, analytics, etc. }; <MarkdownExplorer fileTree={fileTree} onNavigate={handleNavigate} /> \`\`\` ## What's Next? - Explore the [API Reference](/docs/api/overview.md) for detailed configuration options - Check out [Examples](/examples/basic.md) for more implementation patterns - Learn about [Theming](/docs/theming.md) to customize the appearance`, '/docs/api/overview.md': `# API Overview The Markdown Explorer Viewer provides a comprehensive API for customizing and controlling the component behavior. ## Core Components ### MarkdownExplorer The main component that orchestrates the entire experience. **Props:** - \`fileTree\`: FileNode - The hierarchical file structure - \`theme\`: 'light' | 'dark' | 'auto' - Color theme - \`enableSearch\`: boolean - Enable/disable search functionality - \`showBreadcrumbs\`: boolean - Show navigation breadcrumbs - \`onNavigate\`: function - Callback for navigation events ### FileTree Standalone file tree component for custom layouts. **Props:** - \`fileTree\`: FileNode - File structure to display - \`currentPath\`: string - Currently selected path - \`onNodeClick\`: function - Handle file/folder clicks - \`enableSearch\`: boolean - Enable search within tree ### MarkdownViewer Pure markdown rendering component. **Props:** - \`content\`: string - Markdown content to render - \`theme\`: 'light' | 'dark' | 'auto' - Color theme - \`components\`: object - Custom React components for markdown elements ## Data Types ### FileNode \`\`\`typescript interface FileNode { name: string; // Display name path: string; // Full path identifier type: 'file' | 'folder'; // Node type children?: FileNode[]; // Child nodes (folders only) content?: string; // File content (files only) lastModified?: string; // ISO date string size?: number; // File size in bytes } \`\`\` ## Utility Functions ### parseFileTree(files: Record<string, string>): FileNode Converts a flat file structure into a hierarchical tree. \`\`\`typescript const files = { '/docs/readme.md': '# Documentation', '/docs/api/overview.md': '# API Overview' }; const tree = parseFileTree(files); \`\`\` ### findNodeByPath(tree: FileNode, path: string): FileNode | null Locates a specific node in the file tree. \`\`\`typescript const node = findNodeByPath(fileTree, '/docs/api/overview.md'); \`\`\` ### searchNodes(tree: FileNode, query: string): FileNode[] Searches for nodes matching the given query. \`\`\`typescript const results = searchNodes(fileTree, 'api'); \`\`\` ## Event Handling ### Navigation Events \`\`\`typescript const handleNavigate = (path: string, node: FileNode) => { // Called when user navigates to a different file/folder console.log('Navigated to:', path); // Update browser URL window.history.pushState({}, '', \`/docs\${path}\`); // Track analytics analytics.track('documentation_view', { path }); }; \`\`\` ### Search Events Search is handled internally, but you can control it programmatically: \`\`\`typescript // Custom search implementation const MyCustomExplorer = () => { const [searchQuery, setSearchQuery] = useState(''); const filteredTree = useMemo(() => { return searchQuery ? filterTreeByQuery(fileTree, searchQuery) : fileTree; }, [fileTree, searchQuery]); return ( <MarkdownExplorer fileTree={filteredTree} enableSearch={false} // Disable built-in search /> ); }; \`\`\` For more detailed examples, see the [API Reference](/docs/api/reference.md).`, '/docs/api/reference.md': `# API Reference Complete reference for all components, props, and utility functions. ## MarkdownExplorer The primary component providing the complete markdown exploration experience. ### Props | Prop | Type | Default | Description | |------|------|---------|-------------| | \`fileTree\` | \`FileNode\` | - | **Required.** The root node of the file tree | | \`rootPath\` | \`string\` | \`'/'\` | Root path for navigation | | \`theme\` | \`'light' \\| 'dark' \\| 'auto'\` | \`'auto'\` | Color theme | | \`className\` | \`string\` | \`''\` | Additional CSS class | | \`initialRoute\` | \`string\` | - | Initial path to navigate to | | \`onNavigate\` | \`(path: string, node: FileNode) => void\` | - | Navigation callback | | \`enableSearch\` | \`boolean\` | \`true\` | Enable search functionality | | \`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\` | - | Custom file icon renderer | | \`renderFolderIcon\` | \`(node: FileNode) => ReactNode\` | - | Custom folder icon renderer | | \`sidebarWidth\` | \`string\` | \`'280px'\` | Sidebar width CSS value | | \`showBreadcrumbs\` | \`boolean\` | \`true\` | Show breadcrumb navigation | | \`markdownComponents\` | \`Record<string, ComponentType>\` | - | Custom markdown components | ### Usage Examples #### Basic Setup \`\`\`tsx <MarkdownExplorer fileTree={myFileTree} /> \`\`\` #### With Custom Theme \`\`\`tsx <MarkdownExplorer fileTree={myFileTree} theme="dark" className="documentation-explorer" /> \`\`\` #### With Navigation Handling \`\`\`tsx <MarkdownExplorer fileTree={myFileTree} initialRoute="/docs/introduction.md" onNavigate={(path, node) => { console.log('Navigated to:', path); // Update router, analytics, etc. }} /> \`\`\` ## FileTree Standalone file tree navigation component. ### Props | Prop | Type | Default | Description | |------|------|---------|-------------| | \`fileTree\` | \`FileNode\` | - | **Required.** Root file tree node | | \`currentPath\` | \`string\` | - | Currently selected path | | \`theme\` | \`'light' \\| 'dark' \\| 'auto'\` | \`'light'\` | Color theme | | \`onNodeClick\` | \`(node: FileNode) => void\` | - | **Required.** Node click handler | | \`enableSearch\` | \`boolean\` | \`true\` | Enable search functionality | | \`searchPlaceholder\` | \`string\` | \`'Search files...'\` | Search input placeholder | | \`showIcons\` | \`boolean\` | \`true\` | Show file/folder icons | | \`renderFileIcon\` | \`(node: FileNode) => ReactNode\` | - | Custom file icon renderer | | \`renderFolderIcon\` | \`(node: FileNode) => ReactNode\` | - | Custom folder icon renderer | | \`className\` | \`string\` | \`''\` | Additional CSS class | ## MarkdownViewer Pure markdown content renderer. ### Props | Prop | Type | Default | Description | |------|------|---------|-------------| | \`content\` | \`string\` | - | **Required.** Markdown content | | \`theme\` | \`'light' \\| 'dark' \\| 'auto'\` | \`'light'\` | Color theme | | \`className\` | \`string\` | \`''\` | Additional CSS class | | \`components\` | \`Record<string, ComponentType>\` | - | Custom markdown components | | \`filePath\` | \`string\` | - | File path for relative links | ### Custom Components Example \`\`\`tsx const customComponents = { h1: ({ children }) => ( <h1 style={{ color: '#2563eb', borderBottom: '2px solid #2563eb' }}> {children} </h1> ), code: ({ children, className }) => ( <code className={className} style={{ background: '#f1f5f9' }}> {children} </code> ), a: ({ href, children }) => ( <a href={href} target="_blank" rel="noopener noreferrer"> {children} ↗ </a> ) }; <MarkdownViewer content={markdownContent} components={customComponents} /> \`\`\` ## Breadcrumbs Navigation breadcrumb component. ### Props | Prop | Type | Default | Description | |------|------|---------|-------------| | \`path\` | \`string\` | - | **Required.** Current path | | \`rootPath\` | \`string\` | \`'/'\` | Root path | | \`theme\` | \`'light' \\| 'dark' \\| 'auto'\` | \`'light'\` | Color theme | | \`onPathClick\` | \`(path: string) => void\` | - | **Required.** Path click handler | | \`className\` | \`string\` | \`''\` | Additional CSS class | ## TypeScript Types ### FileNode \`\`\`typescript interface FileNode { name: string; path: string; type: 'file' | 'folder'; children?: FileNode[]; content?: string; lastModified?: string; size?: number; } \`\`\` ### Theme \`\`\`typescript type Theme = 'light' | 'dark' | 'auto'; \`\`\` ### NavigationState \`\`\`typescript interface NavigationState { currentPath: string; currentNode: FileNode | null; history: string[]; historyIndex: number; } \`\`\` ## Utility Functions ### parseFileTree(files: Record<string, string>): FileNode Converts flat file structure to hierarchical tree. ### findNodeByPath(tree: FileNode, path: string): FileNode | null Finds node by path in the tree. ### searchNodes(tree: FileNode, query: string): FileNode[] Searches nodes matching the query. ### generateBreadcrumbs(path: string, rootPath?: string): Array<{name: string, path: string}> Generates breadcrumb items from path. ### isMarkdownFile(filename: string): boolean Checks if file is a markdown file. ### getFileExtension(filename: string): string Extracts file extension from filename. ### normalizePath(path: string): string Normalizes path format. --- This concludes the complete API reference. For examples and guides, see the other documentation sections.`, '/docs/theming.md': `# Theming Guide Learn how to customize the appearance of the Markdown Explorer Viewer to match your application's design. ## Built-in Themes The component comes with three theme options: - \`light\` - Clean, bright theme suitable for most applications - \`dark\` - Dark theme for low-light environments - \`auto\` - Automatically detects system preference ### Setting a Theme \`\`\`tsx // Light theme <MarkdownExplorer fileTree={fileTree} theme="light" /> // Dark theme <MarkdownExplorer fileTree={fileTree} theme="dark" /> // Auto-detect system preference <MarkdownExplorer fileTree={fileTree} theme="auto" /> \`\`\` ## CSS Custom Properties The component uses CSS custom properties (variables) for easy theming: ### Light Theme Variables \`\`\`css :root { --me-primary: #2563eb; --me-primary-hover: #1d4ed8; --me-text-primary: #1f2937; --me-text-secondary: #6b7280; --me-text-muted: #9ca3af; --me-bg-primary: #ffffff; --me-bg-secondary: #f9fafb; --me-bg-tertiary: #f3f4f6; --me-border: #e5e7eb; --me-border-hover: #d1d5db; --me-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); --me-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); --me-radius: 0.375rem; --me-radius-lg: 0.5rem; --me-transition: all 0.15s ease-in-out; --me-sidebar-width: 280px; --me-code-bg: #f8fafc; --me-code-border: #e2e8f0; } \`\`\` ### Dark Theme Variables \`\`\`css [data-theme="dark"] { --me-text-primary: #f9fafb; --me-text-secondary: #d1d5db; --me-text-muted: #9ca3af; --me-bg-primary: #111827; --me-bg-secondary: #1f2937; --me-bg-tertiary: #374151; --me-border: #374151; --me-border-hover: #4b5563; --me-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3); --me-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3); --me-code-bg: #1e293b; --me-code-border: #334155; } \`\`\` ## Custom Styling ### Using CSS Classes You can add custom styling using the \`className\` prop: \`\`\`tsx <MarkdownExplorer fileTree={fileTree} className="my-custom-explorer" /> \`\`\` \`\`\`css .my-custom-explorer { border: 2px solid #3b82f6; border-radius: 12px; overflow: hidden; } /* Customize sidebar */ .my-custom-explorer .sidebar { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } /* Customize file tree */ .my-custom-explorer .file-tree { padding: 1rem; } \`\`\` ### Overriding CSS Variables Create your own theme by overriding the CSS variables: \`\`\`css .purple-theme { --me-primary: #8b5cf6; --me-primary-hover: #7c3aed; --me-bg-secondary: #faf5ff; --me-border: #e9d5ff; } \`\`\` \`\`\`tsx <MarkdownExplorer fileTree={fileTree} className="purple-theme" /> \`\`\` ## Custom Markdown Components Customize how markdown elements are rendered: \`\`\`tsx const customComponents = { h1: ({ children }) => ( <h1 style={{ color: '#8b5cf6', borderBottom: '3px solid #8b5cf6', paddingBottom: '0.5rem' }}> {children} </h1> ), h2: ({ children }) => ( <h2 style={{ color: '#6366f1', marginTop: '2rem' }}> 🎯 {children} </h2> ), code: ({ children, className }) => ( <code className={className} style={{ background: '#f1f5f9', padding: '2px 6px', borderRadius: '4px', border: '1px solid #cbd5e1' }} > {children} </code> ), blockquote: ({ children }) => ( <blockquote style={{ borderLeft: '4px solid #8b5cf6', background: 'linear-gradient(90deg, #faf5ff 0%, #f3f4f6 100%)', padding: '1rem', margin: '1rem 0', borderRadius: '0 8px 8px 0' }}> {children} </blockquote> ), table: ({ children }) => ( <div style={{ overflowX: 'auto', margin: '1rem 0' }}> <table style={{ width: '100%', borderCollapse: 'collapse', border: '2px solid #e5e7eb', borderRadius: '8px', overflow: 'hidden' }}> {children} </table> </div> ) }; <MarkdownExplorer fileTree={fileTree} markdownComponents={customComponents} /> \`\`\` ## Icon Customization ### Custom File Icons \`\`\`tsx const renderFileIcon = (node: FileNode) => { const ext = node.name.split('.').pop()?.toLowerCase(); switch (ext) { case 'md': case 'markdown': return <span style={{ color: '#22c55e' }}>📝</span>; case 'js': case 'jsx': return <span style={{ color: '#f7df1e' }}>🟨</span>; case 'ts': case 'tsx': return <span style={{ color: '#3178c6' }}>🟦</span>; case 'css': return <span style={{ color: '#1572b6' }}>🎨</span>; default: return <span style={{ color: '#6b7280' }}>📄</span>; } }; const renderFolderIcon = (node: FileNode) => { return <span style={{ color: '#f59e0b' }}>📁</span>; }; <MarkdownExplorer fileTree={fileTree} renderFileIcon={renderFileIcon} renderFolderIcon={renderFolderIcon} /> \`\`\` ## Responsive Design The component is responsive by default, but you can customize breakpoints: \`\`\`css /* Custom mobile styles */ @media (max-width: 768px) { .my-explorer { --me-sidebar-width: 100%; } } /* Custom tablet styles */ @media (max-width: 1024px) { .my-explorer { --me-sidebar-width: 240px; } } \`\`\` ## Advanced Theming ### Theme Switching \`\`\`tsx function ThemeableExplorer() { const [theme, setTheme] = useState<'light' | 'dark' | 'auto'>('auto'); return ( <div> <div style={{ padding: '1rem' }}> <label> Theme: <select value={theme} onChange={(e) => setTheme(e.target.value as any)}> <option value="light">Light</option> <option value="dark">Dark</option> <option value="auto">Auto</option> </select> </label> </div> <MarkdownExplorer fileTree={fileTree} theme={theme} /> </div> ); } \`\`\` ### Dynamic Styling \`\`\`tsx function DynamicExplorer() { const [accentColor, setAccentColor] = useState('#2563eb'); const customStyle = { '--me-primary': accentColor, '--me-primary-hover': adjustBrightness(accentColor, -20) } as React.CSSProperties; return ( <div style={customStyle}> <input type="color" value={accentColor} onChange={(e) => setAccentColor(e.target.value)} /> <MarkdownExplorer fileTree={fileTree} /> </div> ); } \`\`\` This comprehensive theming system allows you to create a markdown explorer that perfectly matches your application's design language!`, '/examples/basic.md': `# Basic Examples This page contains basic implementation examples for the Markdown Explorer Viewer. ## Minimal Setup The simplest way to get started: \`\`\`tsx import { MarkdownExplorer, parseFileTree } from '@asafarim/markdown-explorer-viewer'; const files = { '/README.md': '# Hello World\\n\\nWelcome to my documentation!' }; function App() { return ( <MarkdownExplorer fileTree={parseFileTree(files)} /> ); } \`\`\` ## Multiple Files and Folders Creating a more complex file structure: \`\`\`tsx const documentationFiles = { '/README.md': '# Project Documentation\\n\\nOverview of the project.', '/getting-started.md': '# Getting Started\\n\\nQuick start guide.', '/guides/installation.md': '# Installation\\n\\nHow to install the project.', '/guides/configuration.md': '# Configuration\\n\\nConfiguring your setup.', '/api/overview.md': '# API Overview\\n\\nIntroduction to our API.', '/api/endpoints.md': '# API Endpoints\\n\\nComplete endpoint reference.', '/examples/basic.md': '# Basic Examples\\n\\nSimple usage examples.', '/examples/advanced.md': '# Advanced Examples\\n\\nComplex use cases.' }; const fileTree = parseFileTree(documentationFiles); function DocumentationSite() { return ( <div style={{ height: '100vh' }}> <MarkdownExplorer fileTree={fileTree} initialRoute="/README.md" enableSearch={true} showBreadcrumbs={true} /> </div> ); } \`\`\` ## With Theme Toggle Adding a theme switcher: \`\`\`tsx import { useState } from 'react'; function ThemedDocumentation() { const [theme, setTheme] = useState<'light' | 'dark'>('light'); return ( <div> <header style={{ padding: '1rem', background: theme === 'dark' ? '#1f2937' : '#f9fafb' }}> <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> Switch to {theme === 'light' ? 'Dark' : 'Light'} Theme </button> </header> <div style={{ height: 'calc(100vh - 70px)' }}> <MarkdownExplorer fileTree={fileTree} theme={theme} /> </div> </div> ); } \`\`\` ## Custom File Icons Customizing how files appear in the tree: \`\`\`tsx const customFileIcon = (node: FileNode) => { const extension = node.name.split('.').pop()?.toLowerCase(); const iconMap = { 'md': '📖', 'js': '💛', 'ts': '💙', 'json': '⚙️', 'css': '🎨', 'html': '🌐', 'png': '🖼️', 'jpg': '🖼️', 'gif': '🖼️' }; return <span>{iconMap[extension] || '📄'}</span>; }; const customFolderIcon = (node: FileNode) => { const folderNames = { 'api': '🔌', 'docs': '📚', 'examples': '💡', 'guides': '📋', 'assets': '📦' }; return <span>{folderNames[node.name] || '📁'}</span>; }; <MarkdownExplorer fileTree={fileTree} renderFileIcon={customFileIcon} renderFolderIcon={customFolderIcon} /> \`\`\` ## Navigation Handling Integrating with routing and analytics: \`\`\`tsx import { useNavigate } from 'react-router-dom'; function RoutedDocumentation() { const navigate = useNavigate(); const handleNavigate = (path: string, node: FileNode) => { // Update browser URL navigate(\`/docs\${path}\`); // Track page views if (typeof gtag !== 'undefined') { gtag('config', 'GA_MEASUREMENT_ID', { page_title: node.name, page_location: window.location.href }); } // Update document title document.title = \`\${node.name} - Documentation\`; }; return ( <MarkdownExplorer fileTree={fileTree} onNavigate={handleNavigate} /> ); } \`\`\` ## Responsive Layout Creating a mobile-friendly layout: \`\`\`tsx import { useState, useEffect } from 'react'; function ResponsiveDocumentation() { const [isMobile, setIsMobile] = useState(false); const [sidebarVisible, setSidebarVisible] = useState(true); useEffect(() => { const checkMobile = () => { const mobile = window.innerWidth < 768; setIsMobile(mobile); setSidebarVisible(!mobile); }; checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); return ( <div> {isMobile && ( <header style={{ padding: '1rem', background: '#f3f4f6' }}> <button onClick={() => setSidebarVisible(!sidebarVisible)}> {sidebarVisible ? 'Hide' : 'Show'} Files </button> </header> )} <MarkdownExplorer fileTree={fileTree} showFileTree={sidebarVisible} sidebarWidth={isMobile ? '100%' : '280px'} /> </div> ); } \`\`\` ## Search Integration Adding custom search functionality: \`\`\`tsx import { useState, useMemo } from 'react'; import { searchNodes } from '@asafarim/markdown-explorer-viewer'; function SearchableDocumentation() { const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState<FileNode[]>([]); const handleSearch = (query: string) => { setSearchQuery(query); if (query.trim()) { const results = searchNodes(fileTree, query); setSearchResults(results); } else { setSearchResults([]); } }; return ( <div> <div style={{ padding: '1rem', borderBottom: '1px solid #e5e7eb' }}> <input type="text" placeholder="Search documentation..." value={searchQuery} onChange={(e) => handleSearch(e.target.value)} style={{ width: '100%', padding: '0.5rem', border: '1px solid #d1d5db', borderRadius: '0.375rem' }} /> {searchResults.length > 0 && ( <div style={{ marginTop: '1rem' }}> <h4>Search Results ({searchResults.length})</h4> <ul> {searchResults.map(node => ( <li key={node.path}> <strong>{node.name}</strong> - {node.path} </li> ))} </ul> </div> )} </div> <MarkdownExplorer fileTree={fileTree} enableSearch={false} // Use custom search instead /> </div> ); } \`\`\` These examples should give you a solid foundation for implementing the Markdown Explorer Viewer in your own projects!`, '/examples/advanced.md': `# Advanced Examples This page demonstrates advanced usage patterns and integration techniques. ## Dynamic Content Loading Loading markdown content from an API: \`\`\`tsx import { useState, useEffect } from 'react'; import { FileNode } from '@asafarim/markdown-explorer-viewer'; function DynamicDocumentation() { const [fileTree, setFileTree] = useState<FileNode | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { async function loadDocumentation() { try { // Load file list from API const response = await fetch('/api/documentation/files'); const fileList = await response.json(); // Build file tree with lazy loading const tree = buildFileTreeWithLazyLoading(fileList); setFileTree(tree); } catch (error) { console.error('Failed to load documentation:', error); } finally { setLoading(false); } } loadDocumentation(); }, []); const handleNavigate = async (path: string, node: FileNode) => { if (node.type === 'file' && !node.content) { // Lazy load content when file is accessed try { const response = await fetch(\`/api/documentation/content?path=\${encodeURIComponent(path)}\`); const content = await response.text(); node.content = content; // Force re-render by updating the tree setFileTree({ ...fileTree! }); } catch (error) { console.error('Failed to load file content:', error); node.content = 'Error loading content'; } } }; if (loading) { return <div>Loading documentation...</div>; } if (!fileTree) { return <div>Failed to load documentation</div>; } return ( <MarkdownExplorer fileTree={fileTree} onNavigate={handleNavigate} /> ); } function buildFileTreeWithLazyLoading(fileList: string[]): FileNode { // Implementation details... return { name: 'Documentation', path: '/', type: 'folder', children: fileList.map(filePath => ({ name: filePath.split('/').pop()!, path: filePath, type: 'file' as const, // content will be loaded lazily })) }; } \`\`\` ## Multi-language Documentation Supporting multiple languages: \`\`\`tsx interface MultiLanguageProps { languages: { code: string; name: string }[]; defaultLanguage: string; } function MultiLanguageDocumentation({ languages, defaultLanguage }: MultiLanguageProps) { const [currentLanguage, setCurrentLanguage] = useState(defaultLanguage); const [fileTrees, setFileTrees] = useState<Record<string, FileNode>>({}); useEffect(() => { async function loadLanguageFiles() { const trees: Record<string, FileNode> = {}; for (const lang of languages) { const response = await fetch(\`/api/docs/\${lang.code}/files\`); const files = await response.json(); trees[lang.code] = parseFileTree(files); } setFileTrees(trees); } loadLanguageFiles(); }, [languages]); const currentFileTree = fileTrees[currentLanguage]; return ( <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}> <header style={{ padding: '1rem', borderBottom: '1px solid #e5e7eb' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}> <h1>Documentation</h1> <select value={currentLanguage} onChange={(e) => setCurrentLanguage(e.target.value)} > {languages.map(lang => ( <option key={lang.code} value={lang.code}> {lang.name} </option> ))} </select> </div> </header> <div style={{ flex: 1 }}> {currentFileTree && ( <MarkdownExplorer key={currentLanguage} // Force re-render on language change fileTree={currentFileTree} initialRoute={\`/README.\${currentLanguage}.md\`} /> )} </div> </div> ); } \`\`\` ## Version-aware Documentation Supporting multiple documentation versions: \`\`\`tsx interface VersionedDocumentationProps { versions: { version: string; label: string; default?: boolean }[]; } function VersionedDocumentation({ versions }: VersionedDocumentationProps) { const [currentVersion, setCurrentVersion] = useState( versions.find(v => v.default)?.version || versions[0]?.version ); const [fileTree, setFileTree] = useState<FileNode | null>(null); useEffect(() => { async function loadVersionFiles() { const response = await fetch(\`/api/docs/\${currentVersion}/structure\`); const files = await response.json(); setFileTree(parseFileTree(files)); } if (currentVersion) { loadVersionFiles(); } }, [currentVersion]); const handleNavigate = async (path: string, node: FileNode) => { if (node.type === 'file' && !node.content) { const response = await fetch(\`/api/docs/\${currentVersion}/content?path=\${encodeURIComponent(path)}\`); const content = await response.text(); node.content = content; setFileTree({ ...fileTree! }); } }; return ( <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}> <header style={{ padding: '1rem', background: '#1f2937', color: 'white', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <h1>Project Documentation</h1> <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}> <label style={{ fontSize: '0.875rem' }}>Version:</label> <select value={currentVersion} onChange={(e) => setCurrentVersion(e.target.value)} style={{ padding: '0.25rem 0.5rem', borderRadius: '0.25rem' }} > {versions.map(version => ( <option key={version.version} value={version.version}> {version.label} </option> ))} </select> </div> </header> <div style={{ flex: 1 }}> {fileTree && ( <MarkdownExplorer key={currentVersion} fileTree={fileTree} onNavigate={handleNavigate} theme="dark" /> )} </div> </div> ); } \`\`\` ## Custom Markdown Plugins Adding custom markdown processing: \`\`\`tsx import { useMemo } from 'react'; function CustomMarkdownExplorer() { const customComponents = useMemo(() => ({ // Custom code block with copy functionality pre: ({ children, ...props }: any) => { const [copied, setCopied] = useState(false); const handleCopy = async () => { const code = children.props.children; await navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return ( <div style={{ position: 'relative' }}> <pre {...props} style={{ paddingTop: '2.5rem' }}> {children} </pre> <button onClick={handleCopy} style={{ position: 'absolute', top: '0.5rem', right: '0.5rem', padding: '0.25rem 0.5rem', background: copied ? '#10b981' : '#6b7280', color: 'white', border: 'none', borderRadius: '0.25rem', fontSize: '0.75rem' }} > {copied ? 'Copied!' : 'Copy'} </button> </div> ); }, // Custom callout boxes blockquote: ({ children }: any) => { const content = children.props.children; const isCallout = content.startsWith('[!'); if (isCallout) { const type = content.match(/\\[!(\\w+)\\]/)?.[1]?.toLowerCase(); const text = content.replace(/\\[!\\w+\\]\\s*/, ''); const calloutStyles = { note: { border: '#3b82f6', bg: '#dbeafe' }, warning: { border: '#f59e0b', bg: '#fef3c7' }, danger: { border: '#ef4444', bg: '#fee2e2' }, tip: { border: '#10b981', bg: '#d1fae5' } }; const style = calloutStyles[type] || calloutStyles.note; return ( <div style={{ border: \`2px solid \${style.border}\`, background: style.bg, padding: '1rem', borderRadius: '0.5rem', margin: '1rem 0' }}> <div style={{ fontWeight: 'bold', textTransform: 'uppercase', fontSize: '0.875rem', color: style.border, marginBottom: '0.5rem' }}> {type || 'Note'} </div> {text} </div> ); } return <blockquote>{children}</blockquote>; }, // Interactive checkboxes for task lists input: ({ type, checked, ...props }: any) => { if (type === 'checkbox') { return ( <input type="checkbox" checked={checked} onChange={() => {}} // Make readonly for demo style={{ marginRight: '0.5rem' }} {...props} /> ); } return <input type={type} {...props} />; } }), []); return ( <MarkdownExplorer fileTree={fileTree} markdownComponents={customComponents} /> ); } \`\`\` ## Integration with State Management Using with Redux or Zustand: \`\`\`tsx // Zustand store import { create } from 'zustand'; interface DocumentationStore { currentPath: string; history: string[]; bookmarks: string[]; setCurrentPath: (path: string) => void; addBookmark: (path: string) => void; removeBookmark: (path: string) => void; goBack: () => void; goForward: () => void; } const useDocumentationStore = create<DocumentationStore>((set, get) => ({ currentPath: '/', history: ['/'], bookmarks: [], setCurrentPath: (path) => set(state => ({ currentPath: path, history: [...state.history, path] })), addBookmark: (path) => set(state => ({ bookmarks: [...state.bookmarks, path] })), removeBookmark: (path) => set(state => ({ bookmarks: state.bookmarks.filter(b => b !== path) })), goBack: () => set(state => { const newHistory = state.history.slice(0, -1); return { history: newHistory, currentPath: newHistory[newHistory.length - 1] || '/' }; }), goForward: () => { // Implementation for forward navigation } })); function StateManagedDocumentation() { const { currentPath, bookmarks, setCurrentPath, addBookmark, removeBookmark, goBack } = useDocumentationStore(); const handleNavigate = (path: string, node: FileNode) => { setCurrentPath(path); }; const isBookmarked = bookmarks.includes(currentPath); return ( <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}> <header style={{ padding: '1rem', borderBottom: '1px solid #e5e7eb' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}> <button onClick={goBack}>← Back</button> <span>Current: {currentPath}</span> <button onClick={() => isBookmarked ? removeBookmark(currentPath) : addBookmark(currentPath)} > {isBookmarked ? '★' : '☆'} Bookmark </button> </div> {bookmarks.length > 0 && ( <div style={{ marginTop: '0.5rem' }}> <strong>Bookmarks:</strong> {bookmarks.map(bookmark => ( <button key={bookmark} onClick={() => setCurrentPath(bookmark)} style={{ marginLeft: '0.5rem', fontSize: '0.875rem' }} > {bookmark} </button> ))} </div> )} </header> <div style={{ flex: 1 }}> <MarkdownExplorer fileTree={fileTree} initialRoute={currentPath} onNavigate={handleNavigate} /> </div> </div> ); } \`\`\` These advanced examples show how to build sophisticated documentation systems with dynamic loading, multi-language support, versioning, and state management integration.`, '/changelog.md': `# Changelog All notable changes to the Markdown Explorer Viewer will be documented in this file. ## [1.0.0] - 2024-01-15 ### 🎉 Initial Release This is the first stable release of the Markdown Explorer Viewer! ### ✨ Features Added - **File Tree Navigation** - Interactive folder and file browser - **Markdown Rendering** - Full GitHub Flavored Markdown support - **Search Functionality** - Real-time search through files and content - **Theme Support** - Light, dark, and auto themes - **Responsive Design** - Mobile-friendly with collapsible sidebar - **Breadcrumb Navigation** - Easy navigation with clickable breadcrumbs - **TypeScript Support** - Complete type definitions included - **Accessibility** - Full keyboard navigation and screen reader support ### 📚 Components - \`MarkdownExplorer\` - Main component with integrated file tree and viewer - \`FileTree\` - Standalone file tree component - \`MarkdownViewer\` - Pure markdown rendering component - \`Breadcrumbs\` - Navigation breadcrumb component ### 🛠️ Utility Functions - \`parseFileTree()\` - Convert flat file structure to hierarchical tree - \`findNodeByPath()\` - Find specific nodes in the file tree - \`searchNodes()\` - Search for nodes matching a query - \`generateBreadcrumbs()\` - Create breadcrumb navigation items - Theme utilities for light/dark mode handling ### 🎨 Styling - CSS modules for component styling - CSS custom properties for easy theming - Responsive design with mobile-first approach - Support for custom icons and styling ### 📦 Dependencies - React 18+ support - React Router integration ready - Minimal external dependencies - Tree-shakeable for optimal bundle size --- ## Future Releases ### Planned for v1.1.0 - **Enhanced Search** - Full-text search with highlighting - **File Metadata** - Display file sizes, modification dates - **Export Functionality** - Export selected files or entire trees - **Performance Improvements** - Virtual scrolling for large file trees - **Plugin System** - Custom markdown processors and renderers ### Planned for v1.2.0 - **Real-time Collaboration** - Multi-user editing and navigation - **Version Control Integration** - Git integration for file history - **Advanced Theming** - Theme builder and more built-in themes - **Internationalization** - Multi-language support for UI ### Planned for v2.0.0 - **Visual Editor** - WYSIWYG markdown editing - **Media Support** - Enhanced image, video, and file handling - **API Integration** - Direct CMS and headless CMS integration - **Advanced Analytics** - Usage tracking and insights --- ## Contributing We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to: - Report bugs - Suggest new features - Submit pull requests - Help with documentation ## Support If you encounter any issues or have questions: - 📝 [Create an issue](https://github.com/AliSafari-IT/asafarim-webapp/issues) - 💬 [Join our discussions](https://github.com/AliSafari-IT/asafarim-webapp/discussions) - 📧 [Contact support](mailto:support@asafarim.com) Thank you for using Markdown Explorer Viewer! 🚀` }; // Add many more sample files to test scrolling for (let i = 1; i <= 20; i++) { sampleFiles[`/docs/item${i}.md`] = `# Item ${i}\n\nThis is test item ${i} for scrolling test.`; } // Add another folder with many files sampleFiles['/examples/basic.md'] = `# Basic Example\n\nThis is a basic example.`; sampleFiles['/examples/advanced.md'] = `# Advanced Example\n\nThis is an advanced example.`; for (let i = 1; i <= 15; i++) { sampleFiles[`/examples/example${i}.md`] = `# Example ${i}\n\nThis is example ${i} for scrolling test.`; } export const createSampleFileTree = () => { return parseFileTree(sampleFiles); };