import { booleanAttribute, Component, computed, HostListener, inject, input, signal, } from '@angular/core'; import { DomSanitizer, type SafeHtml } from '@angular/platform-browser'; import { Router } from '@angular/router'; import { AfricaniesIconComponent } from '@africanies/africanies-icons'; import { ButtonComponent, CopyButtonComponent } from '@africanies/africanies-ui'; import { highlightSnippetWithGlossary } from '../lecture/link-glossary-terms'; /** * Labeled demo canvas for showcasing a component variant group. * Optional `code` input reveals a Show code / Hide code toggle with a * syntax-highlighted, copyable implementation snippet. */ @Component({ selector: 'app-demo-section', standalone: true, imports: [ButtonComponent, AfricaniesIconComponent, CopyButtonComponent], template: `

{{ title() }}

@if (badge()) { {{ badge() }} }
@if (hint()) {

{{ hint() }}

}
@if (hasCode()) { }
@if (subtext()) {

{{ subtext() }}

} @if (hasCode() && codeOpen()) {
Implementation TypeScript
}
`, }) export class DemoSectionComponent { private readonly sanitizer = inject(DomSanitizer); private readonly router = inject(Router); /** Section heading. */ readonly title = input.required(); /** Optional id for deep links (`/models#country`). */ readonly anchorId = input(null); /** Optional supporting sentence under the title. */ readonly hint = input(null); /** * Optional gotcha / clarification under the live demo * (e.g. “`color` never appears in serialized params”). */ readonly subtext = input(null); /** Optional meta chip (e.g. "4 variants"). */ readonly badge = input(null); /** Use dashed muted canvas instead of solid white. */ readonly muted = input(false, { transform: booleanAttribute }); /** * Usage snippet shown when the visitor toggles “Show code”. * Omit (or pass null/empty) to hide the toggle. */ readonly code = input(null); protected readonly codeOpen = signal(false); protected readonly hasCode = computed(() => { const value = this.code(); return value != null && value.trim().length > 0; }); protected readonly highlightedCode = computed((): SafeHtml => { const value = this.code() ?? ''; const html = highlightSnippetWithGlossary(value, 'typescript'); // Snippets are authored in-repo; highlight.js returns escaped markup. return this.sanitizer.bypassSecurityTrustHtml(html); }); protected toggleCode(): void { this.codeOpen.update((open) => !open); } /** * In-snippet glossary links use plain `` tags — route in-app without reload. * @param event */ @HostListener('click', ['$event']) protected onGlossaryLinkClick(event: MouseEvent): void { const anchor = (event.target as HTMLElement).closest('a.pg-glossary-link'); if (!(anchor instanceof HTMLAnchorElement)) { return; } event.preventDefault(); const url = new URL(anchor.href, window.location.origin); void this.router.navigate([url.pathname], { fragment: url.hash.replace(/^#/, '') || undefined, }); } }