feat(design): re-theme to CIBG Huisstijl (Bootstrap 5.2), replacing RHC/Utrecht
The portal now adheres to the CIBG design system (designsystem.cibg.nl) — a customized Bootstrap 5.2 build — replacing the Rijkshuisstijl-Community / Utrecht theme. See ADR-0003. - Vendor @cibg/huisstijl@3.22.0 under public/cibg-huisstijl/, loaded via a <link> in index.html (Storybook serves it via staticDirs). Drop the two @rijkshuisstijl-community deps. - Token bridge in styles.scss: redefine the app's ~54 --rhc-* tokens onto CIBG/--bs-* values, so components keep referencing tokens (no 300+ site rewrite). System-font stack; licensed RO/Rijks text fonts intentionally not shipped. - Re-skin every shared atom to Bootstrap/CIBG classes (btn, form-control, form-check-*, table, breadcrumb, …) keeping their input() APIs. alert is hand-rolled (CIBG drops .alert); local .card/.badge renamed to avoid Bootstrap collisions. - Domain pages: drop stray rhc-*/utrecht-* classes; registration-table → table table-striped. Verified: build green, check:tokens OK, lint clean, 174 tests pass, build-storybook OK, and the served build loads the vendored CSS (200, .btn-primary present). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,16 +2,30 @@ import { Component, input } from '@angular/core';
|
||||
|
||||
type AlertType = 'info' | 'ok' | 'warning' | 'error';
|
||||
|
||||
/** Atom: alert/message banner. */
|
||||
/** Atom: alert/message banner. CIBG's Bootstrap build drops the stock `.alert`, so this
|
||||
is a hand-rolled surface styled from the (CIBG-valued) token bridge — no raw hex. */
|
||||
@Component({
|
||||
selector: 'app-alert',
|
||||
styles: [`
|
||||
.alert-box{
|
||||
border:var(--rhc-border-width-sm) solid;
|
||||
border-inline-start-width:var(--rhc-border-width-md);
|
||||
border-radius:var(--rhc-border-radius-md);
|
||||
padding:var(--rhc-space-max-md) var(--rhc-space-max-lg);
|
||||
color:var(--rhc-color-foreground-default);
|
||||
}
|
||||
.alert-info{background:var(--rhc-color-hemelblauw-100);border-color:var(--rhc-color-foreground-link)}
|
||||
.alert-ok{background:var(--rhc-color-groen-300);border-color:var(--rhc-color-groen-500)}
|
||||
.alert-warning{background:var(--rhc-color-geel-100);border-color:var(--rhc-color-geel-600)}
|
||||
.alert-error{background:var(--rhc-color-rood-100);border-color:var(--rhc-color-rood-500)}
|
||||
`],
|
||||
template: `
|
||||
<div
|
||||
class="utrecht-alert rhc-alert"
|
||||
[class.utrecht-alert--info]="type() === 'info'"
|
||||
[class.utrecht-alert--ok]="type() === 'ok'"
|
||||
[class.utrecht-alert--warning]="type() === 'warning'"
|
||||
[class.utrecht-alert--error]="type() === 'error'"
|
||||
class="alert-box"
|
||||
[class.alert-info]="type() === 'info'"
|
||||
[class.alert-ok]="type() === 'ok'"
|
||||
[class.alert-warning]="type() === 'warning'"
|
||||
[class.alert-error]="type() === 'error'"
|
||||
role="status">
|
||||
<ng-content />
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@ export class AsyncErrorDirective {
|
||||
}
|
||||
@case ('Empty') {
|
||||
@if (emptyTpl()) { <ng-container [ngTemplateOutlet]="emptyTpl()!.tpl" /> }
|
||||
@else { <p class="rhc-paragraph">{{ emptyText() }}</p> }
|
||||
@else { <p>{{ emptyText() }}</p> }
|
||||
}
|
||||
@case ('Success') {
|
||||
<ng-container [ngTemplateOutlet]="loadedTpl().tpl"
|
||||
|
||||
@@ -2,19 +2,18 @@ import { Component, input } from '@angular/core';
|
||||
|
||||
type Variant = 'primary' | 'secondary' | 'subtle' | 'danger';
|
||||
|
||||
/** Atom: button. Thin wrapper over the Utrecht/RHC button CSS. */
|
||||
/** Atom: button. Thin wrapper over the CIBG/Bootstrap button CSS. */
|
||||
@Component({
|
||||
selector: 'app-button',
|
||||
template: `
|
||||
<button
|
||||
[type]="type()"
|
||||
[disabled]="disabled()"
|
||||
class="utrecht-button rhc-button"
|
||||
[class.utrecht-button--primary-action]="variant() === 'primary'"
|
||||
[class.utrecht-button--secondary-action]="variant() === 'secondary'"
|
||||
[class.utrecht-button--subtle]="variant() === 'subtle'"
|
||||
[class.utrecht-button--danger]="variant() === 'danger'"
|
||||
[class.utrecht-button--disabled]="disabled()">
|
||||
class="btn"
|
||||
[class.btn-primary]="variant() === 'primary'"
|
||||
[class.btn-outline-primary]="variant() === 'secondary'"
|
||||
[class.btn-link]="variant() === 'subtle'"
|
||||
[class.btn-danger]="variant() === 'danger'">
|
||||
<ng-content />
|
||||
</button>
|
||||
`,
|
||||
|
||||
@@ -3,13 +3,14 @@ import { HeadingComponent } from '@shared/ui/heading/heading.component';
|
||||
|
||||
/** Molecule: a content card. Standardises the repeated card surface (white,
|
||||
subtle border, rounded, padded) so pages compose cards instead of hand-rolling
|
||||
`<div class="rhc-card">`. Optional heading; the rest is projected. */
|
||||
a hand-rolled card surface. Optional heading; the rest is projected.
|
||||
Local class is .app-card (NOT Bootstrap's .card, whose padding-on-body layout differs). */
|
||||
@Component({
|
||||
selector: 'app-card',
|
||||
imports: [HeadingComponent],
|
||||
styles: [`
|
||||
:host{display:block;block-size:100%}
|
||||
.card{
|
||||
.app-card{
|
||||
background:var(--rhc-color-wit);
|
||||
border:var(--rhc-border-width-sm) solid var(--rhc-color-border-subtle);
|
||||
border-radius:var(--rhc-border-radius-md);
|
||||
@@ -17,10 +18,10 @@ import { HeadingComponent } from '@shared/ui/heading/heading.component';
|
||||
block-size:100%;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.card > * + *{margin-block-start:var(--rhc-space-max-md)}
|
||||
.app-card > * + *{margin-block-start:var(--rhc-space-max-md)}
|
||||
`],
|
||||
template: `
|
||||
<section class="card">
|
||||
<section class="app-card">
|
||||
@if (heading()) { <app-heading [level]="level()">{{ heading() }}</app-heading> }
|
||||
<ng-content />
|
||||
</section>
|
||||
|
||||
@@ -8,11 +8,12 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
styles: [`
|
||||
:host{display:block}
|
||||
label{display:inline-flex;align-items:center;gap:var(--rhc-space-max-md);cursor:pointer}
|
||||
input{inline-size:1.1rem;block-size:1.1rem}
|
||||
/* form-check-input floats/margins for the .form-check layout; here it's flex-aligned. */
|
||||
.form-check-input{margin:0;float:none;inline-size:1.1rem;block-size:1.1rem}
|
||||
`],
|
||||
template: `
|
||||
<label>
|
||||
<input type="checkbox" [id]="checkboxId()" [checked]="value" [disabled]="disabled"
|
||||
<label class="form-check-label">
|
||||
<input class="form-check-input" type="checkbox" [id]="checkboxId()" [checked]="value" [disabled]="disabled"
|
||||
(change)="onToggle($event)" (blur)="onTouched()" />
|
||||
<span>{{ label() }}</span>
|
||||
</label>
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
import { Component, input } from '@angular/core';
|
||||
|
||||
/** Molecule: one key/value row inside an RHC data-summary.
|
||||
Wrap several of these in .rhc-data-summary (see registration-summary). */
|
||||
/** Molecule: one key/value row in a data summary. Hand-rolled from tokens
|
||||
(CIBG has no data-summary component). Wrap several in a <dl> (see registration-summary). */
|
||||
@Component({
|
||||
selector: 'app-data-row',
|
||||
// The RHC item draws a divider below every row; drop it on the last one so the
|
||||
// list ends cleanly instead of looking like a trailing empty row.
|
||||
styles: [`:host:last-child .rhc-data-summary__item { border-block-end-style: none; }`],
|
||||
styles: [`
|
||||
.item{
|
||||
display:grid;
|
||||
grid-template-columns:minmax(8rem, 14rem) 1fr;
|
||||
gap:var(--rhc-space-max-md);
|
||||
padding-block:var(--rhc-space-max-md);
|
||||
border-block-end:var(--rhc-border-width-sm) solid var(--rhc-color-border-subtle);
|
||||
margin:0;
|
||||
}
|
||||
/* drop the divider on the last row so the list ends cleanly */
|
||||
:host:last-child .item{border-block-end-style:none}
|
||||
.item-key{font-weight:var(--rhc-text-font-weight-semi-bold);margin:0}
|
||||
.item-value{margin:0}
|
||||
`],
|
||||
template: `
|
||||
<div class="rhc-data-summary__item">
|
||||
<dt class="rhc-data-summary__item-key">{{ key() }}</dt>
|
||||
<dd class="rhc-data-summary__item-value"><ng-content>{{ value() }}</ng-content></dd>
|
||||
<div class="item">
|
||||
<dt class="item-key">{{ key() }}</dt>
|
||||
<dd class="item-value"><ng-content>{{ value() }}</ng-content></dd>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
|
||||
@@ -12,16 +12,16 @@ import { Component, booleanAttribute, input } from '@angular/core';
|
||||
.error{color:var(--rhc-color-foreground-default);font-weight:var(--rhc-text-font-weight-semi-bold);margin-block-start:var(--rhc-space-max-sm);border-inline-start:var(--rhc-border-width-md) solid var(--rhc-color-rood-500);padding-inline-start:var(--rhc-space-max-md)}
|
||||
`],
|
||||
template: `
|
||||
<div class="rhc-form-field utrecht-form-field" [class.utrecht-form-field--invalid]="!!error()">
|
||||
<label class="utrecht-form-label label" [id]="fieldId() + '-label'" [for]="fieldId()">
|
||||
<div class="form-field">
|
||||
<label class="form-label label" [id]="fieldId() + '-label'" [for]="fieldId()">
|
||||
{{ label() }}@if (required()) { <span class="req" i18n="@@formField.verplicht">(verplicht)</span> }
|
||||
</label>
|
||||
@if (description()) {
|
||||
<div class="utrecht-form-field-description desc" [id]="fieldId() + '-desc'">{{ description() }}</div>
|
||||
<div class="form-text desc" [id]="fieldId() + '-desc'">{{ description() }}</div>
|
||||
}
|
||||
<ng-content />
|
||||
@if (error()) {
|
||||
<div class="utrecht-form-field-error-message error" [id]="fieldId() + '-error'" role="alert">{{ error() }}</div>
|
||||
<div class="error" [id]="fieldId() + '-error'" role="alert">{{ error() }}</div>
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, input } from '@angular/core';
|
||||
import { NgTemplateOutlet } from '@angular/common';
|
||||
|
||||
/** Atom: heading. Renders the right h1..h5 with RHC heading styling.
|
||||
/** Atom: heading. Renders the right h1..h5; CIBG/Bootstrap styles native headings.
|
||||
Single <ng-content> captured in a template — multiple ng-content across
|
||||
@switch branches silently drops the projected content. */
|
||||
@Component({
|
||||
@@ -10,11 +10,11 @@ import { NgTemplateOutlet } from '@angular/common';
|
||||
template: `
|
||||
<ng-template #content><ng-content /></ng-template>
|
||||
@switch (level()) {
|
||||
@case (1) { <h1 class="rhc-heading nl-heading--level-1"><ng-container [ngTemplateOutlet]="content" /></h1> }
|
||||
@case (2) { <h2 class="rhc-heading nl-heading--level-2"><ng-container [ngTemplateOutlet]="content" /></h2> }
|
||||
@case (3) { <h3 class="rhc-heading nl-heading--level-3"><ng-container [ngTemplateOutlet]="content" /></h3> }
|
||||
@case (4) { <h4 class="rhc-heading nl-heading--level-4"><ng-container [ngTemplateOutlet]="content" /></h4> }
|
||||
@default { <h5 class="rhc-heading nl-heading--level-5"><ng-container [ngTemplateOutlet]="content" /></h5> }
|
||||
@case (1) { <h1><ng-container [ngTemplateOutlet]="content" /></h1> }
|
||||
@case (2) { <h2><ng-container [ngTemplateOutlet]="content" /></h2> }
|
||||
@case (3) { <h3><ng-container [ngTemplateOutlet]="content" /></h3> }
|
||||
@case (4) { <h4><ng-container [ngTemplateOutlet]="content" /></h4> }
|
||||
@default { <h5><ng-container [ngTemplateOutlet]="content" /></h5> }
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Component, input } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
/** Atom: link. Internal router link styled as an RHC/Utrecht link. */
|
||||
/** Atom: link. Internal router link; CIBG/Bootstrap styles bare anchors. */
|
||||
@Component({
|
||||
selector: 'app-link',
|
||||
imports: [RouterLink],
|
||||
template: `<a [routerLink]="to()" class="rhc-link nl-link utrecht-link"><ng-content /></a>`,
|
||||
template: `<a [routerLink]="to()" class="link-primary"><ng-content /></a>`,
|
||||
})
|
||||
export class LinkComponent {
|
||||
to = input.required<string>();
|
||||
|
||||
@@ -18,25 +18,27 @@ export const JA_NEE: RadioOption[] = [
|
||||
@Component({
|
||||
selector: 'app-radio-group',
|
||||
styles: [`
|
||||
.rhc-radio-option {
|
||||
.radio-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--rhc-space-max-md);
|
||||
padding-block: var(--rhc-space-max-sm);
|
||||
margin: 0;
|
||||
}
|
||||
/* form-check-input floats/margins for the .form-check layout; here it's flex-aligned. */
|
||||
.radio-option .form-check-input { margin: 0; float: none; }
|
||||
`],
|
||||
template: `
|
||||
<div
|
||||
class="utrecht-form-field-radio-group"
|
||||
role="radiogroup"
|
||||
[attr.aria-labelledby]="name() + '-label'"
|
||||
[attr.aria-invalid]="invalid() ? 'true' : null"
|
||||
[attr.aria-describedby]="invalid() ? name() + '-error' : null">
|
||||
@for (opt of options(); track opt.value) {
|
||||
<label class="utrecht-form-label utrecht-form-label--radio-button rhc-radio-option">
|
||||
<label class="form-check-label radio-option">
|
||||
<input
|
||||
class="utrecht-radio-button"
|
||||
[class.utrecht-radio-button--checked]="value === opt.value"
|
||||
class="form-check-input"
|
||||
[class.is-invalid]="invalid()"
|
||||
type="radio"
|
||||
[name]="name()"
|
||||
[value]="opt.value"
|
||||
|
||||
@@ -52,12 +52,12 @@ export interface PlaceholderOption {
|
||||
template: `
|
||||
@if (editable()) {
|
||||
<div class="rte-toolbar" role="toolbar" [attr.aria-label]="toolbarLabel()">
|
||||
<button type="button" class="utrecht-button utrecht-button--subtle" (mousedown)="$event.preventDefault()" (click)="format('bold')" [attr.aria-label]="boldLabel()"><b>B</b></button>
|
||||
<button type="button" class="utrecht-button utrecht-button--subtle" (mousedown)="$event.preventDefault()" (click)="format('italic')" [attr.aria-label]="italicLabel()"><i>I</i></button>
|
||||
<button type="button" class="utrecht-button utrecht-button--subtle" (mousedown)="$event.preventDefault()" (click)="format('underline')" [attr.aria-label]="underlineLabel()"><u>U</u></button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" (mousedown)="$event.preventDefault()" (click)="format('bold')" [attr.aria-label]="boldLabel()"><b>B</b></button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" (mousedown)="$event.preventDefault()" (click)="format('italic')" [attr.aria-label]="italicLabel()"><i>I</i></button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" (mousedown)="$event.preventDefault()" (click)="format('underline')" [attr.aria-label]="underlineLabel()"><u>U</u></button>
|
||||
<span class="rte-sep" aria-hidden="true"></span>
|
||||
<button type="button" class="utrecht-button utrecht-button--subtle" (mousedown)="$event.preventDefault()" (click)="list('bullet')" [attr.aria-label]="bulletListLabel()">•</button>
|
||||
<button type="button" class="utrecht-button utrecht-button--subtle" (mousedown)="$event.preventDefault()" (click)="list('number')" [attr.aria-label]="numberListLabel()">1.</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" (mousedown)="$event.preventDefault()" (click)="list('bullet')" [attr.aria-label]="bulletListLabel()">•</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" (mousedown)="$event.preventDefault()" (click)="list('number')" [attr.aria-label]="numberListLabel()">1.</button>
|
||||
@if (placeholders().length) {
|
||||
<span class="rte-sep" aria-hidden="true"></span>
|
||||
<label>
|
||||
|
||||
@@ -5,10 +5,14 @@ import { Component, input } from '@angular/core';
|
||||
This keeps the shared UI kernel free of any domain knowledge. */
|
||||
@Component({
|
||||
selector: 'app-status-badge',
|
||||
styles: [`.badge{display:inline-flex;align-items:center;gap:var(--rhc-space-max-md)}`],
|
||||
// Local class is NOT Bootstrap's .badge (which would add pill padding/colour) — hence .status-badge.
|
||||
styles: [`
|
||||
.status-badge{display:inline-flex;align-items:center;gap:var(--rhc-space-max-md)}
|
||||
.dot{inline-size:0.75rem;block-size:0.75rem;border-radius:var(--rhc-border-radius-round);flex:none}
|
||||
`],
|
||||
template: `
|
||||
<span class="badge">
|
||||
<span class="rhc-dot-badge" [style.background-color]="color()" aria-hidden="true"></span>
|
||||
<span class="status-badge">
|
||||
<span class="dot" [style.background-color]="color()" aria-hidden="true"></span>
|
||||
<span>{{ label() }}</span>
|
||||
</span>
|
||||
`,
|
||||
|
||||
@@ -10,8 +10,8 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
`],
|
||||
template: `
|
||||
<input
|
||||
class="utrecht-textbox rhc-text-input"
|
||||
[class.utrecht-textbox--invalid]="invalid()"
|
||||
class="form-control"
|
||||
[class.is-invalid]="invalid()"
|
||||
[type]="type()"
|
||||
[id]="inputId()"
|
||||
[attr.aria-invalid]="invalid() ? 'true' : null"
|
||||
|
||||
@@ -6,20 +6,21 @@ import type { DeliveryChannel } from '@shared/upload/upload.machine';
|
||||
@Component({
|
||||
selector: 'app-delivery-channel-toggle',
|
||||
styles: [`
|
||||
.rhc-radio-option {
|
||||
.radio-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--rhc-space-max-md);
|
||||
padding-block: var(--rhc-space-max-sm);
|
||||
margin: 0;
|
||||
}
|
||||
.radio-option .form-check-input { margin: 0; float: none; }
|
||||
`],
|
||||
template: `
|
||||
<div class="utrecht-form-field-radio-group" role="radiogroup">
|
||||
<div role="radiogroup">
|
||||
@for (opt of options; track opt.value) {
|
||||
<label class="utrecht-form-label utrecht-form-label--radio-button rhc-radio-option">
|
||||
<label class="form-check-label radio-option">
|
||||
<input
|
||||
class="utrecht-radio-button"
|
||||
[class.utrecht-radio-button--checked]="channel() === opt.value"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
[name]="name()"
|
||||
[value]="opt.value"
|
||||
|
||||
@@ -35,9 +35,7 @@ import { Component, ElementRef, input, output, viewChild } from '@angular/core';
|
||||
[multiple]="multiple()"
|
||||
[disabled]="disabled()"
|
||||
(change)="onChange($event)" />
|
||||
<span
|
||||
class="utrecht-button rhc-button utrecht-button--secondary-action label"
|
||||
[class.utrecht-button--disabled]="disabled()">
|
||||
<span class="btn btn-outline-primary label" [class.disabled]="disabled()">
|
||||
<span aria-hidden="true">↑</span>
|
||||
<span>{{ label() }}</span>
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user