Files
atomic-design-poc/apps/ssp/src/app/showcase/concept-card.component.ts
T
ehoandClaude Sonnet 5 630d68045f 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>
2026-09-04 23:44:01 +02:00

106 lines
2.9 KiB
TypeScript

import { Component, input } from '@angular/core';
/** Molecule-shaped teaching card: the showcase's "before/after" box. Renders its own
tag pill and, when `code` is set, the highlighted snippet — everything else comes in
via `<ng-content />`. Angular does not style projected content from the receiving
component, so any other card-shaped block inside a section (a sub-label, a nested
ok/err result) nests another `<app-concept-card>` rather than writing raw `.tag`
markup — that markup would carry the SECTION's scope, not this component's, and the
rule here would never match it. */
@Component({
selector: 'app-concept-card',
imports: [],
styles: [
`
.card {
border: 1px solid var(--rhc-color-grijs-200);
border-radius: 10px;
padding: 1.25rem;
background: var(--rhc-color-wit);
}
.card--bad {
border-color: var(--rhc-color-rood-300);
}
.card--good {
border-color: var(--rhc-color-groen-300);
}
.tag {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-weight: 700;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 0.75rem;
}
.tag::before {
content: '';
width: 0.6rem;
height: 0.6rem;
border-radius: 50%;
}
.tag.bad {
color: var(--rhc-color-rood-600);
}
.tag.bad::before {
background: var(--rhc-color-rood-500);
}
.tag.good {
color: var(--rhc-color-groen-700);
}
.tag.good::before {
background: var(--rhc-color-groen-500);
}
.tag.plain {
color: var(--rhc-color-grijs-700);
}
.tag.plain::before {
display: none;
}
.linked {
margin: 0 0 1rem;
}
.linked .src {
font-size: 0.72rem;
color: var(--rhc-color-grijs-700);
margin: 0.35rem 0 0;
font-family: monospace;
}
`,
],
template: `
<div
class="card"
[class.card--good]="variant() === 'good'"
[class.card--bad]="variant() === 'bad'"
>
<p
class="tag"
[class.good]="variant() === 'good'"
[class.bad]="variant() === 'bad'"
[class.plain]="variant() === 'plain'"
>
{{ tag() }}
</p>
@if (code(); as c) {
@if (src(); as s) {
<figure class="linked">
<pre class="app-code" [innerHTML]="c"></pre>
<figcaption class="src">↳ {{ s }}</figcaption>
</figure>
} @else {
<pre class="app-code" [innerHTML]="c"></pre>
}
}
<ng-content />
</div>
`,
})
export class ConceptCardComponent {
variant = input<'good' | 'bad' | 'plain'>('plain');
tag = input.required<string>();
code = input<string | undefined>();
src = input<string | undefined>();
}