npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mailmarc/react-course-viewer

v1.0.1

Published

A powerful React component for creating interactive learning courses with markdown support

Readme

@mailmarc/react-course-viewer

Test NPM Version NPM Downloads License TypeScript React

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-viewer

Quick 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

  • or Space: Next lesson (if current lesson is complete)
  • : Previous lesson
  • Enter: Mark current lesson as complete
  • Esc: 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 4

Instead 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 preference

CSS 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 documentation

Markdown Content Setup

  1. Copy docs to public directory:

    cd frontend/public && cp -r ../../docs/learning-centre docs/
  2. Or use the provided script:

    cd frontend/public/docs && ./copy_docs.sh
  3. 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

  1. Flattened Lessons: Now shows proper section-based progress
  2. Static Content: Now loads actual markdown files dynamically
  3. Hard-coded Course: Now supports any course configuration
  4. Poor Progress Display: Now shows "Section X - Lesson Y of Z"

🔄 Migration Steps

  1. Replace import:

    // Old
    import CourseViewer from '../components/CourseViewer';
       
    // New  
    import { CourseViewer } from '../components/ui/CourseViewer';
  2. 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();
    }, []);
  3. 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:

  1. Create markdown files in your project's public/docs/ folder structure
  2. Use MarkdownCourseLoader to load markdown-based courses
  3. Or use CourseLoader for programmatic course creation
  4. Extend the Course, Section, or Lesson interfaces for custom features

License

MIT © 2025 Sheridan Computers Limited