Elevasis LogoElevasis Docs

Course Subshell Navigation

Contextual sidebar navigation for course pages using the SubshellSidebar pattern from AI Studio

Implementation:

  • apps/training/src/routes/courses.tsx - Layout route with SubshellContainer
  • apps/training/src/features/courses/sidebar/CourseSidebar.tsx - Contextual sidebar

Overview

When a user navigates to a course (/courses/:courseSlug), the app displays a secondary sidebar showing the course's lessons and assessments. This follows the established SubshellSidebar pattern used by AI Studio's contextual sidebars.

Pattern Reference:

  • apps/command-center/src/routes/ai-studio.tsx - Route layout with SubshellContainer
  • apps/command-center/src/features/ai-studio/sidebar/AISidebarMiddle.tsx - Contextual sidebar switching

Architecture

Layout Structure

SubshellContainer
├── SubshellSidebar (width=260)
│   └── CourseSidebar (contextual based on route)
└── SubshellRightSideContainer
    └── Outlet

Contextual Behavior

The sidebar adapts based on the current route:

RouteSidebar Content
/coursesSimple catalog prompt
/courses/:courseSlugCourse lessons + assessments
/courses/:courseSlug/:lessonSlugSame, with active lesson highlighted

Implementation

Layout Route

// apps/training/src/routes/courses.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
import { SubshellContainer, SubshellRightSideContainer, SubshellSidebar } from '@repo/ui'
import { CourseSidebar } from '../features/courses/sidebar/CourseSidebar'

export const Route = createFileRoute('/courses')({
  component: CoursesLayoutComponent
})

function CoursesLayoutComponent() {
  return (
    <SubshellContainer>
      <SubshellSidebar width={260}>
        <CourseSidebar />
      </SubshellSidebar>
      <SubshellRightSideContainer>
        <Outlet />
      </SubshellRightSideContainer>
    </SubshellContainer>
  )
}

CourseSidebar Component

The sidebar extracts courseSlug from the path and renders contextual navigation:

// apps/training/src/features/courses/sidebar/CourseSidebar.tsx
export function CourseSidebar() {
  const location = useLocation()
  const path = location.pathname

  // Extract courseSlug from path: /courses/:courseSlug/...
  const match = path.match(/^\/courses\/([^/]+)/)
  const courseSlug = match?.[1]

  // If on catalog page, show simple prompt
  if (!courseSlug || path === '/courses' || path === '/courses/') {
    return <CourseCatalogSidebar />
  }

  // If on course/lesson page, show course navigation
  const course = getCourse(courseSlug)
  if (!course) {
    return <CourseCatalogSidebar />
  }

  return <CourseNavigationSidebar course={course} currentPath={path} />
}

The CourseNavigationSidebar renders lessons and assessments with Mantine's NavLink:

  • Course title (clickable, links to course overview)
  • Lessons section with completion indicators
  • Assessments section (if any)
  • Active lesson highlighted via active prop

Child Route Updates

Child routes use SubshellContentContainer instead of AppShellContentContainer:

// apps/training/src/routes/courses/$courseSlug/$lessonSlug.tsx
import { SubshellContentContainer } from '@repo/ui'

function LessonViewer() {
  return (
    <SubshellContentContainer>
      {/* Lesson content */}
    </SubshellContentContainer>
  )
}

File Structure

apps/training/src/
├── routes/
│   └── courses.tsx                    # Layout route with SubshellContainer
│   └── courses/
│       ├── index.tsx                  # Course catalog
│       ├── $courseSlug/
│       │   ├── index.tsx              # Course detail
│       │   └── $lessonSlug.tsx        # Lesson viewer
├── features/
│   └── courses/
│       └── sidebar/
│           └── CourseSidebar.tsx      # Contextual sidebar

Design Philosophy Compliance

PrincipleStatusNotes
KISSReuses existing SubshellSidebar pattern exactly
YAGNIOnly implements needed navigation
Minimal AbstractionsNo new abstractions, just components
Separation of ConcernsSidebar is presentation only


Last Updated: 2026-01-10