Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@mailmarc/react-course-viewer) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
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.sh
Docs 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