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 SubshellContainerapps/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 SubshellContainerapps/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
└── OutletContextual Behavior
The sidebar adapts based on the current route:
| Route | Sidebar Content |
|---|---|
/courses | Simple catalog prompt |
/courses/:courseSlug | Course lessons + assessments |
/courses/:courseSlug/:lessonSlug | Same, 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} />
}Navigation List
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
activeprop
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 sidebarDesign Philosophy Compliance
| Principle | Status | Notes |
|---|---|---|
| KISS | ✅ | Reuses existing SubshellSidebar pattern exactly |
| YAGNI | ✅ | Only implements needed navigation |
| Minimal Abstractions | ✅ | No new abstractions, just components |
| Separation of Concerns | ✅ | Sidebar is presentation only |
Related Documentation
- Training App Overview - App infrastructure
- LMS Features - Full feature documentation
- UI Architecture - SubshellSidebar component reference
Last Updated: 2026-01-10