refactor: split concepts.page into 6 sections, fix dead highlighting (RD-24)
The page held six teaching sections and a 142-line `styles:` block, at 471 effective lines against a limit of 250. It is now 36 lines of composition. Angular scopes a component's CSS to markup that component rendered, so the split had to move each rule to its owner. `concept-card` owns the card vocabulary and renders it. `.app-code`, `.app-lead`, `.app-cols` and `.app-note` become globals, because their targets are projected or arrive through `[innerHTML]`. That constraint exposed a live bug. The syntax-highlighting rules compiled to `pre[_ngcontent-%COMP%] .k[_ngcontent-%COMP%]`, but `highlight-ts` injects the `.k`/`.s`/`.c` spans through `[innerHTML]`, so they carry no scope attribute and the rule never matched. Keywords, strings and comments have always rendered in the plain foreground colour. The rules are global now, on five new `--app-code-*` tokens. Widen the colour guard while here: it scanned only `*.component.ts`, so every `*.page.ts`, `*.section.ts` and `*.step.ts` was invisible to it. That is how this page collected 21 hardcoded colours. One other file needed a fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { Component } from '@angular/core';
|
||||
import type { Resource } from '@angular/core';
|
||||
import { HeadingComponent } from '@shared/ui/heading/heading.component';
|
||||
import { ASYNC } from '@shared/ui/async/async.component';
|
||||
import { SkeletonComponent } from '@shared/ui/skeleton/skeleton.component';
|
||||
import { ConceptCardComponent } from './concept-card.component';
|
||||
import { SNIPPETS } from './snippets.generated';
|
||||
import { highlightTs } from './highlight-ts';
|
||||
|
||||
/** Minimal fake Resource so <app-async> can be driven through every state without HTTP. */
|
||||
function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T> {
|
||||
return {
|
||||
value: () => value as T,
|
||||
status: () => status,
|
||||
error: () => error,
|
||||
hasValue: () => value !== undefined,
|
||||
reload: () => {},
|
||||
} as unknown as Resource<T>;
|
||||
}
|
||||
|
||||
/** Section 2: RemoteData fold. One value with four mutually exclusive states, instead of
|
||||
three loose booleans. Composition-only; owns its own fake resources. Each of the four
|
||||
demo states nests its own `<app-concept-card>` for its label — a plain `.tag` element
|
||||
written here would carry this section's scope, not the card's, and stay unstyled. */
|
||||
@Component({
|
||||
selector: 'app-concepts-remote-data-section',
|
||||
imports: [HeadingComponent, ...ASYNC, SkeletonComponent, ConceptCardComponent],
|
||||
template: `
|
||||
<section class="app-section">
|
||||
<app-heading [level]="2">2 · RemoteData fold</app-heading>
|
||||
<p class="app-lead">
|
||||
Eén waarde met vier elkaar uitsluitende toestanden in plaats van drie losse booleans.
|
||||
</p>
|
||||
<div class="app-cols">
|
||||
<app-concept-card variant="good" tag="Vier toestanden, één molecuul">
|
||||
<div class="app-stack">
|
||||
<app-concept-card variant="plain" tag="Loading">
|
||||
<app-async [resource]="loadingRes"
|
||||
><ng-template appAsyncLoaded let-v>{{ v }}</ng-template
|
||||
><ng-template appAsyncLoading
|
||||
><app-skeleton [count]="2" height="1.2rem" [delay]="0" /></ng-template
|
||||
></app-async>
|
||||
</app-concept-card>
|
||||
<app-concept-card variant="plain" tag="Empty">
|
||||
<app-async [resource]="emptyRes" [isEmpty]="isEmpty"
|
||||
><ng-template appAsyncLoaded let-v>{{ v }}</ng-template></app-async
|
||||
>
|
||||
</app-concept-card>
|
||||
<app-concept-card variant="plain" tag="Failure">
|
||||
<app-async [resource]="errorRes"
|
||||
><ng-template appAsyncLoaded let-v>{{ v }}</ng-template></app-async
|
||||
>
|
||||
</app-concept-card>
|
||||
<app-concept-card variant="plain" tag="Success">
|
||||
<app-async [resource]="successRes" [isEmpty]="isEmpty"
|
||||
><ng-template appAsyncLoaded
|
||||
><ul>
|
||||
@for (i of successRes.value(); track i) {
|
||||
<li>{{ i }}</li>
|
||||
}
|
||||
</ul></ng-template
|
||||
></app-async
|
||||
>
|
||||
</app-concept-card>
|
||||
</div>
|
||||
</app-concept-card>
|
||||
<app-concept-card
|
||||
variant="good"
|
||||
tag="De exhaustieve fold"
|
||||
[code]="code['fold']"
|
||||
[src]="src['fold']"
|
||||
>
|
||||
<p class="app-note">
|
||||
Een nieuwe variant toevoegen breekt de compile via <code>assertNever</code> tot je hem
|
||||
afhandelt.
|
||||
</p>
|
||||
</app-concept-card>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
})
|
||||
export class RemoteDataSection {
|
||||
isEmpty = (v: string[]) => !v || v.length === 0;
|
||||
|
||||
loadingRes = fakeResource<string[]>('loading');
|
||||
emptyRes = fakeResource<string[]>('resolved', []);
|
||||
errorRes = fakeResource<string[]>('error', undefined, new Error('Demo'));
|
||||
successRes = fakeResource<string[]>('resolved', ['Huisartsgeneeskunde', 'Spoedeisende hulp']);
|
||||
|
||||
protected readonly code: Record<string, string> = { fold: highlightTs(SNIPPETS['fold']) };
|
||||
protected readonly src: Record<string, string> = { fold: 'shared/application/remote-data.ts' };
|
||||
}
|
||||
Reference in New Issue
Block a user