@mailmarc/react-course-viewer
v1.0.1
Published
A powerful React component for creating interactive learning courses with markdown support
Maintainers
Readme
@mailmarc/react-course-viewer
A powerful React component library for creating interactive learning courses with markdown content. Build engaging educational experiences with section-based navigation, progress tracking, achievements, and celebration effects.
Features
✅ Section-based Navigation: Proper course structure with sections and lessons
✅ Dynamic Content Loading: Load markdown files from docs folders
✅ Progress Tracking: Persistent progress with localStorage
✅ Achievement System: Built-in achievements for course milestones
✅ Keyboard Navigation: Arrow keys, Space, Enter support
✅ Touch Gestures: Swipe navigation on mobile devices
✅ Theme Support: Light/dark/system themes
✅ Responsive Design: Works on desktop and mobile
✅ Accessibility: WCAG compliant with focus management
✅ TypeScript: Full type safety and IntelliSense
Installation
npm install @mailmarc/react-course-viewer
# or
yarn add @mailmarc/react-course-viewer
# or
pnpm add @mailmarc/react-course-viewerQuick Start
1. Basic Usage
import { CourseViewer, CourseLoader } from '@mailmarc/react-course-viewer';
function MyTrainingPage() {
const [course, setCourse] = useState(null);
useEffect(() => {
const loadCourse = async () => {
const loader = new CourseLoader();
const course = await loader.loadCourse(COURSE_CONFIGS.dmarc);
setCourse(course);
};
loadCourse();
}, []);
if (!course) return <div>Loading...</div>;
return (
<CourseViewer
course={course}
onComplete={(cert) => handleCourseComplete(cert)}
theme="light"
autoAdvance={true}
/>
);
}2. With Loading States
import { DMARCCourseExample } from '../components/ui/CourseViewer';
function LearningPage() {
return <DMARCCourseExample />;
}3. Custom Course
const customCourse = {
id: 'security-basics',
title: 'Security Basics',
description: 'Learn fundamental security concepts',
version: '1.0.0',
sections: [
{
id: 'intro',
title: 'Introduction',
slug: 'intro',
order: 1,
lessons: [
{
id: 'what-is-security',
title: 'What is Security?',
slug: 'what-is-security',
content: '# What is Security?\n\nSecurity protects your data...',
duration: 5,
order: 1,
},
],
},
],
};
<CourseViewer course={customCourse} />CourseViewer Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| course | Course | required | Course data structure |
| onComplete | (cert: Certificate) => void | - | Called when course is completed |
| onProgress | (progress: CourseProgress) => void | - | Called when progress updates |
| theme | 'light' \| 'dark' \| 'system' | 'light' | UI theme |
| autoAdvance | boolean | true | Auto-advance after lesson completion |
| autoAdvanceDelay | number | 2 | Seconds to wait before auto-advance |
| enableKeyboardNavigation | boolean | true | Enable keyboard shortcuts |
| enableSwipeGestures | boolean | true | Enable touch gestures |
| enableSoundEffects | boolean | false | Play sounds for achievements |
| className | string | '' | Additional CSS classes |
Course Data Structure
Course Type
interface Course {
id: string; // Unique course identifier
title: string; // Course display title
description: string; // Course description
version: string; // Course version
sections: Section[]; // Course sections
metadata?: {
author: string;
difficulty: 'beginner' | 'intermediate' | 'advanced';
estimatedHours: number;
};
}Section Type
interface Section {
id: string; // Unique section identifier
title: string; // Section display title
slug: string; // URL-friendly section name
description?: string; // Optional section description
lessons: Lesson[]; // Lessons in this section
order: number; // Display order
}Lesson Type
interface Lesson {
id: string; // Unique lesson identifier
title: string; // Lesson display title
slug: string; // URL-friendly lesson name
content: string; // Markdown content
duration?: number; // Estimated reading time (minutes)
tags?: string[]; // Topic tags
order: number; // Display order within section
canMarkComplete?: boolean; // Whether lesson can be marked complete
}CourseLoader
The CourseLoader class handles loading markdown files from the filesystem and converting them into course data.
Configuration
const courseConfig = {
id: 'my-course',
title: 'My Course',
description: 'Learn amazing things',
version: '1.0.0',
docsPath: '/docs/learning-centre', // Path to markdown files
sectionsConfig: [
{
id: '01-introduction',
title: 'Introduction',
order: 1,
lessons: [
{
id: '01-intro-basics',
title: 'Basics',
fileName: 'basics.md', // Actual markdown file name
duration: 5,
order: 1,
},
],
},
],
};
const loader = new CourseLoader();
const course = await loader.loadCourse(courseConfig);Built-in Configurations
import { COURSE_CONFIGS } from '../components/ui/CourseViewer';
// DMARC course with proper docs structure
const dmarcCourse = await loader.loadCourse(COURSE_CONFIGS.dmarc);Progress Tracking
Progress is automatically saved to localStorage and includes:
- Current section and lesson position
- Completed lessons list
- Time spent per lesson
- Earned achievements
- Course start and last access times
// Access progress data
const progress = {
courseId: 'dmarc-expert-course',
currentSectionIndex: 2,
currentLessonIndex: 1,
completedLessons: ['lesson-1', 'lesson-2', 'lesson-3'],
achievements: [{ id: 'first-step', name: 'First Step!', ... }],
timeSpent: { 'lesson-1': 5, 'lesson-2': 3 },
startedAt: new Date(),
lastAccessed: new Date(),
};Navigation
Keyboard Shortcuts
→orSpace: Next lesson (if current lesson is complete)←: Previous lessonEnter: Mark current lesson as completeEsc: Menu (future feature)
Touch Gestures
- Swipe Left: Next lesson (if current lesson is complete)
- Swipe Right: Previous lesson
Section-Based Navigation
Unlike the old flattened approach, the new CourseViewer shows:
Section 2: SPF (Safe Postman Friends) - Lesson 3 of 4Instead of:
Lesson 7 of 15 ❌ (old flattened approach)Achievements
Built-in achievements are unlocked automatically:
- 👶 First Step: Complete your first lesson
- 📬 Quarter Master: 25% course completion
- 🦸 Halfway Hero: 50% course completion
- 🚀 Almost There: 75% course completion
- 🎓 Course Expert: 100% course completion
Theming
The component supports three themes:
<CourseViewer theme="light" /> // Light mode
<CourseViewer theme="dark" /> // Dark mode
<CourseViewer theme="system" /> // Follow system preferenceCSS custom properties can be overridden:
.course-viewer[data-theme="custom"] {
--course-primary: #your-color;
--course-background: #your-bg;
/* etc. */
}Package Structure
@mailmarc/react-course-viewer/
├── dist/
│ ├── index.js # CommonJS bundle
│ ├── index.esm.js # ESM bundle
│ ├── package-index.d.ts # TypeScript definitions
│ └── styles.css # Component styles
├── src/
│ ├── index.tsx # Main CourseViewer component
├── types.ts # TypeScript types
├── CourseLoader.ts # Dynamic content loading
├── useProgress.ts # Progress tracking hook
├── CourseViewer.css # Component styles
├── examples.tsx # Usage examples
└── README.md # This documentationMarkdown Content Setup
Copy docs to public directory:
cd frontend/public && cp -r ../../docs/learning-centre docs/Or use the provided script:
cd frontend/public/docs && ./copy_docs.shDocs structure should match config:
public/docs/learning-centre/ ├── 01-introduction/ │ ├── what-is-dmarc.md │ ├── why-email-authentication-matters.md │ └── dmarc-vs-spf-vs-dkim.md ├── 02-spf/ │ └── spf-basics.md └── ...
Migration from Old CourseViewer
The new CourseViewer fixes several issues from the old implementation:
✅ Fixed Issues
- Flattened Lessons: Now shows proper section-based progress
- Static Content: Now loads actual markdown files dynamically
- Hard-coded Course: Now supports any course configuration
- Poor Progress Display: Now shows "Section X - Lesson Y of Z"
🔄 Migration Steps
Replace import:
// Old import CourseViewer from '../components/CourseViewer'; // New import { CourseViewer } from '../components/ui/CourseViewer';Update usage:
// Old (static course) <CourseViewer course={LOADED_DMARC_COURSE} /> // New (dynamic loading) const [course, setCourse] = useState(null); useEffect(() => { const loadCourse = async () => { const loader = new CourseLoader(); const course = await loader.loadCourse(COURSE_CONFIGS.dmarc); setCourse(course); }; loadCourse(); }, []);Add loading states:
if (!course) return <div>Loading course...</div>;
Browser Compatibility
- ✅ Chrome 90+
- ✅ Firefox 88+
- ✅ Safari 14+
- ✅ Edge 90+
- ✅ Mobile browsers
Accessibility Features
- ✅ Keyboard navigation
- ✅ Focus management
- ✅ ARIA labels
- ✅ Screen reader support
- ✅ High contrast themes
- ✅ Reduced motion support
Performance
- Bundle size: ~50KB gzipped (component only, excludes peer dependencies)
- Rendering: Optimized with React.memo and useMemo
- Content loading: Lazy loading of markdown files
- Progress tracking: Debounced localStorage updates
Contributing
To add a new course:
- Create markdown files in your project's
public/docs/folder structure - Use
MarkdownCourseLoaderto load markdown-based courses - Or use
CourseLoaderfor programmatic course creation - Extend the
Course,Section, orLessoninterfaces for custom features
License
MIT © 2025 Sheridan Computers Limited
