Course Curriculum Outline
blockeducationLMS and video course syllabus block: hero header with course metadata, instructor profile, rating, progress bar, and action buttons, collapsible modular section accordions with lesson durations, type badges, and completion checks, plus a sidebar with verifiable certificate preview, downloadable course resources, and student community access.
Installation
$pnpm dlx uipkge-ng@latest add course-curriculum-outline$npx uipkge-ng@latest add course-curriculum-outline$yarn dlx uipkge-ng@latest add course-curriculum-outline$bunx uipkge-ng@latest add course-curriculum-outlineManifest:
https://uipkge.dev/r/angular/course-curriculum-outline.jsonInstalls to:src/app/components/blocks/course-curriculum-outline/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
Lessoninterface Lesson {
id: string
title: string
duration: string
type: 'Video' | 'Interactive Lab' | 'Quiz'
status: 'completed' | 'in-progress' | 'not-started' | 'locked'
}Moduleinterface Module {
id: string
title: string
duration: string
lessonsCount: string
completedCount: number
totalCount: number
status: 'completed' | 'in-progress' | 'locked'
lessons: Lesson[]
}CourseResourceinterface CourseResource {
id: string
title: string
meta: string
type: 'figma' | 'repo' | 'pdf'
}Files installed (2)
src/app/components/blocks/course-curriculum-outline/course-curriculum-outline.component.ts29.9 kB
// LMS video-course syllabus: hero header with course metadata, instructor profile, // rating, progress bar and actions, collapsible module accordions with lesson // durations, type badges and completion checks, plus a sidebar with a verifiable // certificate preview, downloadable resources and community access. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { Award, Check, CheckCircle2, Circle, Clock, Download, ExternalLink, FileCode, FileQuestion, FileText, FlaskConical, GraduationCap, Lock, LucideAngularModule, MessageSquare, PenTool, Play, PlayCircle, ShieldCheck, Star, Users, Video, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiAccordionComponent, UiAccordionContentComponent, UiAccordionItemComponent, UiAccordionTriggerComponent, } from '@/ui/accordion/accordion.component' import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiProgressComponent } from '@/ui/progress/progress.component' export interface Lesson { id: string title: string duration: string type: 'Video' | 'Interactive Lab' | 'Quiz' status: 'completed' | 'in-progress' | 'not-started' | 'locked' } export interface Module { id: string title: string duration: string lessonsCount: string completedCount: number totalCount: number status: 'completed' | 'in-progress' | 'locked' lessons: Lesson[] } interface CourseResource { id: string title: string meta: string type: 'figma' | 'repo' | 'pdf' } export const defaultModules: Module[] = [ { id: 'module-1', title: 'Module 1: Design Tokens & OKLCH Architecture', duration: '1h 45m', lessonsCount: '4 lessons', completedCount: 4, totalCount: 4, status: 'completed', lessons: [ { id: 'm1-l1', title: 'Introduction to OKLCH & Gamut Mapping', duration: '18m', type: 'Video', status: 'completed', }, { id: 'm1-l2', title: 'Configuring Semantic Tokens in Tailwind CSS v4', duration: '26m', type: 'Video', status: 'completed', }, { id: 'm1-l3', title: 'Lab: Building an Automated Contrast Checker', duration: '35m', type: 'Interactive Lab', status: 'completed', }, { id: 'm1-l4', title: 'Quiz: Token Architecture & Dark Mode Strategy', duration: '26m', type: 'Quiz', status: 'completed', }, ], }, { id: 'module-2', title: 'Module 2: Polymorphic Primitives & Headless Reka UI', duration: '3h 10m', lessonsCount: '6 lessons', completedCount: 3, totalCount: 6, status: 'in-progress', lessons: [ { id: 'm2-l1', title: 'Anatomy of Headless Primitives with Reka UI & Radix', duration: '24m', type: 'Video', status: 'completed', }, { id: 'm2-l2', title: 'Building Polymorphic Button & Slot Mechanics', duration: '32m', type: 'Video', status: 'completed', }, { id: 'm2-l3', title: 'Lab: Compound Component Patterns with Provide/Inject', duration: '40m', type: 'Interactive Lab', status: 'completed', }, { id: 'm2-l4', title: 'Accessible Dialog, Popover & Focus Trapping', duration: '36m', type: 'Video', status: 'in-progress', }, { id: 'm2-l5', title: 'Virtualization for High-Density Data Grids', duration: '38m', type: 'Video', status: 'not-started', }, { id: 'm2-l6', title: 'Quiz: Accessibility Tree & ARIA Roles', duration: '20m', type: 'Quiz', status: 'not-started', }, ], }, { id: 'module-3', title: 'Module 3: Micro-Interactions & Spring Physics', duration: '4h 50m', lessonsCount: '8 lessons', completedCount: 0, totalCount: 8, status: 'locked', lessons: [ { id: 'm3-l1', title: 'Physics-Based Animation Principles with Spring Physics', duration: '28m', type: 'Video', status: 'locked', }, { id: 'm3-l2', title: 'Gesture Interactions & Drag-and-Drop Primitives', duration: '35m', type: 'Interactive Lab', status: 'locked', }, { id: 'm3-l3', title: 'Layout Animations and Shared Element Transitions', duration: '42m', type: 'Video', status: 'locked', }, { id: 'm3-l4', title: 'Haptic Feedback & Tactile States in Web Interfaces', duration: '25m', type: 'Video', status: 'locked', }, { id: 'm3-l5', title: 'Optimizing 60fps / 120fps Rendering Pipelines', duration: '45m', type: 'Video', status: 'locked', }, { id: 'm3-l6', title: 'Lab: Crafting a Fluid Floating Command Palette', duration: '50m', type: 'Interactive Lab', status: 'locked', }, { id: 'm3-l7', title: 'Sound Design & Audio Micro-Feedback', duration: '25m', type: 'Video', status: 'locked' }, { id: 'm3-l8', title: 'Quiz: Motion Reduction & Animation Performance', duration: '20m', type: 'Quiz', status: 'locked', }, ], }, { id: 'module-4', title: 'Module 4: Monorepo Architecture & CI Distribution', duration: '4h 45m', lessonsCount: '6 lessons', completedCount: 0, totalCount: 6, status: 'locked', lessons: [ { id: 'm4-l1', title: 'Turborepo & pnpm Workspaces Setup from Scratch', duration: '35m', type: 'Video', status: 'locked', }, { id: 'm4-l2', title: 'Dual-Framework Export Pipelines (Vue 3 + React 19)', duration: '45m', type: 'Video', status: 'locked', }, { id: 'm4-l3', title: 'Registry Distribution Schema & JSON Manifest Generator', duration: '50m', type: 'Video', status: 'locked', }, { id: 'm4-l4', title: 'Lab: Custom CLI Tooling with npx shadcn add', duration: '55m', type: 'Interactive Lab', status: 'locked', }, { id: 'm4-l5', title: 'Automated Visual Regression CI with Playwright', duration: '40m', type: 'Interactive Lab', status: 'locked', }, { id: 'm4-l6', title: 'Capstone Project: Publishing Your Own Component Registry', duration: '60m', type: 'Interactive Lab', status: 'locked', }, ], }, ] const resources: CourseResource[] = [ { id: 'res-figma', title: 'Figma Design System Kit v4.2', meta: '.fig · 48.2 MB', type: 'figma' }, { id: 'res-repo', title: 'Course Starter Monorepo Template', meta: 'GitHub Template · Public', type: 'repo' }, { id: 'res-tokens', title: 'OKLCH & Color Tokens Cheatsheet', meta: 'PDF Guide · 2.4 MB', type: 'pdf' }, { id: 'res-wcag', title: 'WCAG 2.2 AA Contrast Matrix', meta: 'PDF Reference · 1.8 MB', type: 'pdf' }, ] export const totalLessonsCount = 24 @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-course-curriculum-outline, [ui-course-curriculum-outline]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiAccordionComponent, UiAccordionContentComponent, UiAccordionItemComponent, UiAccordionTriggerComponent, UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, UiProgressComponent, ], template: ` <div data-slot="course-curriculum-outline" [class]="rootClass"> <!-- Course Hero Header --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-header class="space-y-4"> <div class="flex flex-wrap items-center justify-between gap-3"> <div class="flex flex-wrap items-center gap-2"> <span ui-badge variant="secondary" class="font-medium">Advanced · 14.5 Total Hours</span> <span ui-badge variant="outline" class="text-muted-foreground">Design Engineering</span> </div> <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <div class="text-warning flex items-center"> <lucide-icon [img]="Star" class="size-3.5 fill-current" /> <span class="text-foreground ml-1 font-semibold">4.9</span> </div> <span>·</span> <span class="tabular-nums">(1,840 students)</span> </div> </div> <div class="space-y-2"> <h2 ui-card-title class="text-2xl font-bold tracking-tight sm:text-3xl"> Full-Stack Design Systems with Vue & React </h2> <p ui-card-description class="text-sm"> Master enterprise-grade UI components, OKLCH color spaces, headless architecture with Reka UI, fluid spring physics, and monorepo package distribution. </p> </div> <div class="flex flex-wrap items-center justify-between gap-4 pt-1"> <div class="flex items-center gap-3"> <ui-avatar class="border-border size-10 border"> <ui-avatar-image src="https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=96&h=96&fit=crop&crop=faces" alt="Marcus Vance" /> <ui-avatar-fallback>MV</ui-avatar-fallback> </ui-avatar> <div> <p class="text-sm leading-none font-medium">Marcus Vance</p> <p class="text-muted-foreground mt-1 text-xs">Principal Design Engineer</p> </div> </div> <div class="flex flex-wrap items-center gap-2.5"> @if (!isFullyCompleted()) { <button ui-button class="gap-2 shadow-xs"> <lucide-icon [img]="Play" class="size-4 fill-current" /> Resume Learning </button> } @else { <button ui-button aria-label="Download attachment" class="gap-2 shadow-xs"> <lucide-icon [img]="Award" class="size-4" /> Download Certificate </button> } <button ui-button aria-label="Download attachment" variant="outline" class="gap-2"> <lucide-icon [img]="Download" class="size-4" /> Download Resources </button> </div> </div> </div> <div ui-card-content class="pt-0"> <div class="border-border/60 bg-muted/40 space-y-2 rounded-lg border p-4"> <div class="flex items-center justify-between text-xs"> <span class="text-foreground font-medium"> <span class="tabular-nums">{{ completedLessonsCount() }} of {{ totalLessonsCount }}</span> lessons completed </span> <span class="text-muted-foreground font-medium tabular-nums">{{ progressPercent() }}%</span> </div> <div ui-progress [value]="progressPercent()"></div> </div> </div> </div> <!-- Main Content & Sidebar Grid --> <div class="grid grid-cols-1 gap-6 lg:grid-cols-3"> <!-- Left Column: Modular Curriculum Accordions --> <div class="space-y-4 lg:col-span-2"> <div class="flex items-center justify-between px-1"> <div> <h2 class="text-lg font-semibold tracking-tight">Course Curriculum</h2> <p class="text-muted-foreground text-xs">4 modular sections · 24 total lessons & interactive labs</p> </div> <div class="text-muted-foreground text-xs tabular-nums"> <span class="text-foreground font-medium">{{ completedLessonsCount() }}/{{ totalLessonsCount }}</span> Completed </div> </div> <ui-accordion type="multiple" [defaultValue]="openModules" variant="separated"> @for (module of modules(); track module.id) { <ui-accordion-item [value]="module.id"> <button ui-accordion-trigger class="hover:no-underline"> <div class="flex flex-1 items-center justify-between gap-3 pr-2 text-left"> <div class="flex min-w-0 items-center gap-3"> @if (module.status === 'completed') { <span class="border-success/30 bg-success/15 text-success flex size-7 shrink-0 items-center justify-center rounded-full border shadow-xs" > <lucide-icon [img]="Check" class="size-3.5" /> </span> } @else if (module.status === 'in-progress') { <span class="border-primary/20 bg-primary/10 text-primary flex size-7 shrink-0 items-center justify-center rounded-full border" > <lucide-icon [img]="Play" class="size-3.5 fill-current" /> </span> } @else { <span class="border-border bg-muted text-muted-foreground flex size-7 shrink-0 items-center justify-center rounded-full border" > <lucide-icon [img]="Lock" class="size-3.5" /> </span> } <div class="min-w-0"> <p class="text-foreground truncate text-sm font-semibold">{{ module.title }}</p> <p class="text-muted-foreground mt-0.5 text-xs tabular-nums"> {{ module.lessonsCount }} · {{ module.duration }} · @if (module.status === 'completed') { <span class="text-success font-medium">100% completed</span> } @else if (module.status === 'in-progress') { <span class="text-foreground font-medium"> {{ module.completedCount }}/{{ module.totalCount }} completed </span> } @else { <span class="text-muted-foreground">Locked</span> } </p> </div> </div> <div class="hidden shrink-0 items-center gap-2 sm:flex"> @if (module.status === 'completed') { <span ui-badge variant="success" class="gap-1 text-xs"> <lucide-icon [img]="Check" class="size-3" /> Completed </span> } @else if (module.status === 'in-progress') { <span ui-badge variant="secondary" class="text-xs"> In Progress ({{ module.completedCount }}/{{ module.totalCount }}) </span> } @else { <span ui-badge variant="outline" class="text-muted-foreground gap-1 text-xs"> <lucide-icon [img]="Lock" class="size-3" /> Locked </span> } </div> </div> </button> <ui-accordion-content> <div class="border-border divide-border divide-y border-t"> @for (lesson of module.lessons; track lesson.id) { <div class="group hover:bg-muted/40 flex items-center justify-between gap-3 px-2 py-3 transition-colors first:pt-3 last:pb-1 sm:px-3" > <div class="flex min-w-0 items-center gap-3"> <div class="flex shrink-0 items-center justify-center"> @if (lesson.status === 'completed') { <lucide-icon [img]="CheckCircle2" class="text-success size-4" /> } @else if (lesson.status === 'in-progress') { <lucide-icon [img]="PlayCircle" class="text-primary size-4 animate-pulse" /> } @else if (lesson.status === 'locked') { <lucide-icon [img]="Lock" class="text-muted-foreground/50 size-4" /> } @else { <lucide-icon [img]="Circle" class="text-muted-foreground/60 size-4" /> } </div> <div class="min-w-0"> <p [class]="lessonTitleClass(lesson)">{{ lesson.title }}</p> </div> </div> <div class="flex shrink-0 items-center gap-2"> @if (lesson.type === 'Interactive Lab') { <span ui-badge variant="secondary" class="gap-1 text-xs"> <lucide-icon [img]="FlaskConical" class="size-3" /> <span class="hidden sm:inline">Interactive Lab</span> <span class="sm:hidden">Lab</span> </span> } @else if (lesson.type === 'Quiz') { <span ui-badge variant="outline" class="gap-1 text-xs"> <lucide-icon [img]="FileQuestion" class="size-3" /> Quiz </span> } @else { <span ui-badge variant="outline" class="gap-1 text-xs"> <lucide-icon [img]="Video" class="size-3" /> Video </span> } <span class="text-muted-foreground min-w-[32px] text-right font-mono text-xs tabular-nums"> {{ lesson.duration }} </span> <div class="hidden sm:block"> @if (lesson.status === 'in-progress') { <button ui-button size="sm" class="h-7 gap-1 px-2.5 text-xs"> <lucide-icon [img]="Play" class="size-3 fill-current" /> Resume </button> } @else if (lesson.status === 'completed') { <button ui-button variant="ghost" size="sm" class="text-muted-foreground hover:text-foreground h-7 px-2 text-xs" > Review </button> } @else if (lesson.status === 'not-started') { <button ui-button variant="outline" size="sm" class="h-7 px-2 text-xs">Start</button> } @else { <span class="text-muted-foreground/50 px-2 text-xs">Locked</span> } </div> </div> </div> } </div> </ui-accordion-content> </ui-accordion-item> } </ui-accordion> </div> <!-- Right Column: Certificate & Resources Sidebar --> <div class="space-y-6"> <!-- Certificate Card --> <div ui-card> <div ui-card-header class="pb-3"> <div class="flex items-center gap-2"> <lucide-icon [img]="GraduationCap" class="text-primary size-5" /> <h3 ui-card-title class="text-base">Certificate of Completion</h3> </div> <p ui-card-description class="text-xs">Accredited credential upon 100% course and lab completion.</p> </div> <div ui-card-content class="space-y-4"> <!-- Cert preview container --> <div class="border-border/80 from-muted/50 to-muted/20 relative space-y-3 overflow-hidden rounded-lg border bg-gradient-to-b p-4 text-center" > <div class="relative z-10 space-y-2"> <div [class]="certBadgeClass"> <lucide-icon [img]="Award" class="size-6" /> </div> <div> <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Verified Credential </p> <p class="text-foreground mt-0.5 text-sm font-bold">Full-Stack Design Systems</p> <p class="text-muted-foreground text-xs">Marcus Vance · UIPKGE Academy</p> </div> <div class="pt-1"> <span class="border-border bg-background/80 text-muted-foreground inline-flex items-center gap-1 rounded border px-2 py-0.5 font-mono text-xs" > <lucide-icon [img]="ShieldCheck" class="text-primary size-3" /> CERT-8841-FS </span> </div> </div> </div> <!-- Progress info --> <div class="space-y-1.5"> <div class="flex items-center justify-between text-xs"> <span class="text-muted-foreground">Graduation Status</span> <span class="text-foreground font-medium tabular-nums"> {{ completedLessonsCount() }}/{{ totalLessonsCount }} Lessons </span> </div> <div ui-progress [value]="progressPercent()"></div> <p class="text-muted-foreground pt-1 text-xs"> @if (isFullyCompleted()) { <span class="text-success font-medium"> All requirements satisfied! Your certificate is ready to download and share. </span> } @else { <span> Complete the remaining {{ totalLessonsCount - completedLessonsCount() }} lessons and labs to unlock. </span> } </p> </div> </div> <div ui-card-footer> @if (isFullyCompleted()) { <button ui-button aria-label="Download attachment" class="w-full gap-1.5" size="sm"> <lucide-icon [img]="Download" class="size-4" /> Download PDF Certificate </button> } @else { <button ui-button variant="outline" size="sm" class="w-full gap-1.5" disabled> <lucide-icon [img]="Lock" class="size-4" /> Locked (Complete Course) </button> } </div> </div> <!-- Course Resources Card --> <div ui-card> <div ui-card-header class="pb-3"> <div class="flex items-center gap-2"> <lucide-icon [img]="FileCode" class="text-primary size-5" /> <h3 ui-card-title class="text-base">Course Resources</h3> </div> <p ui-card-description class="text-xs">Downloadable starter kits, Figma files, and spec guides.</p> </div> <div ui-card-content class="space-y-2.5"> @for (res of courseResources; track res.id) { <div class="border-border hover:bg-muted/40 flex items-center justify-between gap-3 rounded-md border p-2.5 transition-colors" > <div class="flex min-w-0 items-center gap-2.5"> <span class="bg-muted text-muted-foreground flex size-8 shrink-0 items-center justify-center rounded-md" > @if (res.type === 'repo') { <lucide-icon [img]="FileCode" class="size-4" /> } @else if (res.type === 'figma') { <lucide-icon [img]="PenTool" class="size-4" /> } @else { <lucide-icon [img]="FileText" class="size-4" /> } </span> <div class="min-w-0"> <p class="text-foreground truncate text-xs font-medium">{{ res.title }}</p> <p class="text-muted-foreground text-xs">{{ res.meta }}</p> </div> </div> <button ui-button variant="ghost" size="icon" class="text-muted-foreground hover:text-foreground size-8 shrink-0" > @if (res.type === 'repo') { <lucide-icon [img]="ExternalLink" class="size-4" /> } @else { <lucide-icon [img]="Download" class="size-4" /> } <span class="sr-only">Download {{ res.title }}</span> </button> </div> } </div> </div> <!-- Community & Mentorship Card --> <div ui-card> <div ui-card-header class="pb-3"> <div class="flex items-center gap-2"> <lucide-icon [img]="Users" class="text-primary size-5" /> <h3 ui-card-title class="text-base">Student Community</h3> </div> <p ui-card-description class="text-xs"> Connect with peers and get code reviews from design engineering mentors. </p> </div> <div ui-card-content class="text-muted-foreground space-y-2.5 text-xs"> <div class="flex items-center gap-2"> <span class="bg-success size-2 rounded-full"></span> <span><strong class="text-foreground">1,420 students online</strong> in Discord</span> </div> <div class="flex items-center gap-2"> <lucide-icon [img]="Clock" class="text-muted-foreground size-3.5" /> <span>Weekly live Q&A: <strong class="text-foreground">Thursdays @ 5:00 PM UTC</strong></span> </div> </div> <div ui-card-footer> <button ui-button variant="outline" size="sm" class="w-full gap-1.5"> <lucide-icon [img]="MessageSquare" class="size-4" /> Join Discord Community </button> </div> </div> </div> </div> </div> `, }) export class UiCourseCurriculumOutlineComponent { protected readonly Award = Award protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly Circle = Circle protected readonly Clock = Clock protected readonly Download = Download protected readonly ExternalLink = ExternalLink protected readonly FileCode = FileCode protected readonly FileQuestion = FileQuestion protected readonly FileText = FileText protected readonly FlaskConical = FlaskConical protected readonly GraduationCap = GraduationCap protected readonly Lock = Lock protected readonly MessageSquare = MessageSquare protected readonly PenTool = PenTool protected readonly Play = Play protected readonly PlayCircle = PlayCircle protected readonly ShieldCheck = ShieldCheck protected readonly Star = Star protected readonly Users = Users protected readonly Video = Video protected readonly openModules = ['module-1', 'module-2'] protected readonly courseResources = resources protected readonly totalLessonsCount = totalLessonsCount /** Seed completed state for all lessons to preview the verified certificate state. */ @Input() set initialComplete(v: boolean) { this.initialCompleteSig.set(!!v) } @Input('class') className?: string private readonly initialCompleteSig = signal(false) readonly modules = computed<Module[]>(() => { if (!this.initialCompleteSig()) return defaultModules return defaultModules.map((m) => ({ ...m, status: 'completed' as const, completedCount: m.totalCount, lessons: m.lessons.map((l) => ({ ...l, status: 'completed' as const })), })) }) readonly completedLessonsCount = computed(() => (this.initialCompleteSig() ? 24 : 8)) readonly progressPercent = computed(() => Math.round((this.completedLessonsCount() / totalLessonsCount) * 100)) readonly isFullyCompleted = computed(() => this.completedLessonsCount() === totalLessonsCount) get rootClass(): string { return cn('text-foreground w-full space-y-6', this.className) } get certBadgeClass(): string { return cn( 'mx-auto flex size-12 items-center justify-center rounded-full border shadow-xs', this.isFullyCompleted() ? 'border-success/30 bg-success/15 text-success' : 'border-primary/30 bg-primary/10 text-primary', ) } lessonTitleClass(lesson: Lesson): string { return cn( 'truncate text-sm font-medium transition-colors', lesson.status === 'locked' ? 'text-muted-foreground' : 'text-foreground group-hover:text-primary', ) } }src/app/components/blocks/course-curriculum-outline/index.ts0.2 kB
export { UiCourseCurriculumOutlineComponent, defaultModules, totalLessonsCount, type Lesson, type Module, } from './course-curriculum-outline.component'
Raw manifest:https://uipkge.dev/r/angular/course-curriculum-outline.json