Spaces:
Sleeping
Sleeping
| import React from 'react'; | |
| import ReactMarkdown from 'react-markdown'; | |
| import { BlogSection as BlogSectionType } from '../types'; | |
| import MermaidDiagram from './MermaidDiagram'; | |
| import InteractiveChart from './InteractiveChart'; | |
| import EquationBlock from './EquationBlock'; | |
| import Collapsible from './Collapsible'; | |
| import Tooltip from './Tooltip'; | |
| import { Info, Lightbulb, AlertTriangle, BookOpen } from 'lucide-react'; | |
| interface Props { | |
| section: BlogSectionType; | |
| theme: 'light' | 'dark'; | |
| index: number; | |
| } | |
| const BlogSectionComponent: React.FC<Props> = ({ section, theme, index }) => { | |
| const getMarginNoteIcon = (icon?: 'info' | 'warning' | 'tip' | 'note') => { | |
| switch (icon) { | |
| case 'warning': | |
| return <AlertTriangle size={14} className="text-amber-500" />; | |
| case 'tip': | |
| return <Lightbulb size={14} className="text-green-500" />; | |
| case 'info': | |
| return <Info size={14} className="text-blue-500" />; | |
| default: | |
| return <BookOpen size={14} className="text-gray-400" />; | |
| } | |
| }; | |
| // Apply tooltips to content | |
| const renderContent = () => { | |
| if (!section.technicalTerms || section.technicalTerms.length === 0) { | |
| return <ReactMarkdown>{section.content}</ReactMarkdown>; | |
| } | |
| // For complex tooltip integration, we'll render ReactMarkdown | |
| // and let users hover on specially marked terms | |
| return ( | |
| <div className="relative"> | |
| <ReactMarkdown>{section.content}</ReactMarkdown> | |
| {/* Technical Terms Legend */} | |
| {section.technicalTerms.length > 0 && ( | |
| <div className="mt-6 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-xl border border-gray-200 dark:border-gray-700"> | |
| <h5 className="text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3"> | |
| Key Terms | |
| </h5> | |
| <div className="flex flex-wrap gap-2"> | |
| {section.technicalTerms.map((term, idx) => ( | |
| <Tooltip key={idx} term={term.term} definition={term.definition}> | |
| <span className="inline-flex items-center px-2.5 py-1 rounded-lg bg-brand-50 dark:bg-brand-900/20 text-brand-700 dark:text-brand-300 text-sm font-medium cursor-help"> | |
| {term.term} | |
| </span> | |
| </Tooltip> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| return ( | |
| <section | |
| id={`section-${section.id}`} | |
| className="relative scroll-mt-32 animate-in fade-in slide-in-from-bottom-4 duration-700" | |
| style={{ animationDelay: `${index * 100}ms` }} | |
| > | |
| <div className="flex gap-8"> | |
| {/* Main Content */} | |
| <article className="flex-1 min-w-0"> | |
| {/* Section Header */} | |
| <header className="mb-8"> | |
| <div className="flex items-center gap-4 mb-4"> | |
| <span className="flex-shrink-0 w-10 h-10 rounded-xl bg-gradient-to-br from-brand-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg shadow-lg shadow-brand-500/20"> | |
| {index + 1} | |
| </span> | |
| <h2 className="text-2xl md:text-3xl font-display font-bold text-gray-900 dark:text-gray-50 leading-tight"> | |
| {section.title} | |
| </h2> | |
| </div> | |
| <div className="w-20 h-1 bg-gradient-to-r from-brand-500 to-purple-500 rounded-full" /> | |
| </header> | |
| {/* Content */} | |
| <div className="prose prose-lg dark:prose-invert max-w-none | |
| prose-headings:font-display prose-headings:font-bold | |
| prose-h3:text-xl prose-h3:mt-8 prose-h3:mb-4 | |
| prose-p:text-gray-700 prose-p:dark:text-gray-300 prose-p:leading-relaxed | |
| prose-strong:text-gray-900 prose-strong:dark:text-white | |
| prose-a:text-brand-600 prose-a:dark:text-brand-400 prose-a:no-underline hover:prose-a:underline | |
| prose-blockquote:border-l-brand-500 prose-blockquote:bg-gray-50 prose-blockquote:dark:bg-gray-800/50 prose-blockquote:py-2 prose-blockquote:px-4 prose-blockquote:rounded-r-lg prose-blockquote:not-italic | |
| prose-code:bg-gray-100 prose-code:dark:bg-gray-800 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:before:content-none prose-code:after:content-none | |
| prose-pre:bg-gray-900 prose-pre:dark:bg-black prose-pre:border prose-pre:border-gray-800 | |
| prose-li:text-gray-700 prose-li:dark:text-gray-300 | |
| "> | |
| {renderContent()} | |
| </div> | |
| {/* Visualization */} | |
| {section.visualizationType && section.visualizationType !== 'none' && ( | |
| <div className="my-10"> | |
| <div className="p-6 rounded-2xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 shadow-lg shadow-gray-200/50 dark:shadow-none"> | |
| {section.visualizationType === 'mermaid' && section.visualizationData && ( | |
| <div className="min-h-[200px]"> | |
| <MermaidDiagram chart={section.visualizationData} theme={theme} /> | |
| </div> | |
| )} | |
| {section.visualizationType === 'chart' && section.chartData && ( | |
| <InteractiveChart data={section.chartData} theme={theme} /> | |
| )} | |
| {section.visualizationType === 'equation' && section.visualizationData && ( | |
| <EquationBlock equation={section.visualizationData} label={`${index + 1}`} /> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| {/* Collapsible Deep Dives */} | |
| {section.collapsibleSections && section.collapsibleSections.length > 0 && ( | |
| <div className="mt-8 space-y-4"> | |
| {section.collapsibleSections.map((collapsible, idx) => ( | |
| <Collapsible | |
| key={collapsible.id} | |
| title={collapsible.title} | |
| variant={idx === 0 ? 'deep-dive' : 'default'} | |
| > | |
| <ReactMarkdown>{collapsible.content}</ReactMarkdown> | |
| </Collapsible> | |
| ))} | |
| </div> | |
| )} | |
| </article> | |
| {/* Margin Notes */} | |
| {section.marginNotes && section.marginNotes.length > 0 && ( | |
| <aside className="hidden xl:block w-64 flex-shrink-0"> | |
| <div className="sticky top-32 space-y-4"> | |
| {section.marginNotes.map((note, idx) => ( | |
| <div | |
| key={note.id} | |
| className="p-4 rounded-xl bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-gray-700 animate-in fade-in slide-in-from-right-4 duration-500" | |
| style={{ animationDelay: `${(index * 100) + (idx * 50)}ms` }} | |
| > | |
| <div className="flex items-start gap-3"> | |
| <div className="flex-shrink-0 mt-0.5"> | |
| {getMarginNoteIcon(note.icon)} | |
| </div> | |
| <p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed"> | |
| {note.text} | |
| </p> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </aside> | |
| )} | |
| </div> | |
| {/* Section Divider */} | |
| <div className="my-16 flex items-center gap-4"> | |
| <div className="flex-1 h-px bg-gradient-to-r from-transparent via-gray-300 dark:via-gray-700 to-transparent" /> | |
| <div className="flex gap-1"> | |
| <div className="w-1.5 h-1.5 rounded-full bg-gray-300 dark:bg-gray-700" /> | |
| <div className="w-1.5 h-1.5 rounded-full bg-gray-400 dark:bg-gray-600" /> | |
| <div className="w-1.5 h-1.5 rounded-full bg-gray-300 dark:bg-gray-700" /> | |
| </div> | |
| <div className="flex-1 h-px bg-gradient-to-r from-transparent via-gray-300 dark:via-gray-700 to-transparent" /> | |
| </div> | |
| </section> | |
| ); | |
| }; | |
| export default BlogSectionComponent; | |