fix(dashboard): move Annuleren inside the keuzelijst card
"Annuleren" rendered as a separate line below the grey card — visually disconnected from the choice it belongs to. Fixing this properly means the card can't just be one big <a> anymore: a <button> can't nest inside an anchor (invalid HTML, broken a11y), and choice-link's [choiceActions] slot needs Annuleren to sit inside the same box. choice-link.component.ts now makes the card a <div> always, with the title wrapped in a vendored Bootstrap `.stretched-link` (its ::after overlay keeps the whole card clickable, same as before) instead of the whole box being the anchor. The projected action gets its own `position:relative;z-index:2` (in aanvraag-block.component.ts, which owns that markup) to stay clickable above the stretched-link overlay. Added `:focus-within` on the card to restore the focus-accent CIBG's `:focus` rule would have given the card itself, since focus now lands on the inner title link. Verified: lint/check:tokens/test/build green; drove it end-to-end — clicking anywhere on a card body still resumes the wizard, clicking Annuleren cancels without navigating. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,13 +13,18 @@ const TYPE_LABELS: Record<AanvraagType, string> = {
|
||||
/** Organism: one application on the dashboard's "Mijn aanvragen" list, rendered as
|
||||
a CIBG Huisstijl "keuzelijst" choice (choice-link). Which text/actions it shows
|
||||
is driven by the pure `blockActions(status)` and the status tag; the block only
|
||||
renders. Only a resumable Concept is clickable — a resolved status has nothing
|
||||
to navigate to, so it renders as a plain (non-anchor) row. */
|
||||
renders. Only a resumable Concept is clickable (stretched-link over the whole
|
||||
card) — a resolved status has nothing to navigate to, so it renders as a plain
|
||||
(non-interactive) card. */
|
||||
@Component({
|
||||
selector: 'app-aanvraag-block',
|
||||
imports: [ButtonComponent, ChoiceLinkComponent],
|
||||
// display:contents — see choice-link.component.ts (keeps <li> a direct <ul> child).
|
||||
styles: [`:host{display:contents}`],
|
||||
styles: [`
|
||||
:host{display:contents} /* see choice-link.component.ts (keeps <li> a direct <ul> child) */
|
||||
/* Sits inside the same card as the stretched-link title — needs its own
|
||||
stacking context above the stretched-link overlay (z-index:1) to stay clickable. */
|
||||
.actions{position:relative;z-index:2;margin-block-start:var(--rhc-space-max-sm)}
|
||||
`],
|
||||
template: `
|
||||
<app-choice-link
|
||||
[heading]="typeLabel()"
|
||||
@@ -27,7 +32,7 @@ const TYPE_LABELS: Record<AanvraagType, string> = {
|
||||
[clickable]="actions().includes('resume')"
|
||||
(activate)="resume.emit()">
|
||||
@if (actions().includes('cancel')) {
|
||||
<div choiceActions>
|
||||
<div choiceActions class="actions">
|
||||
<app-button variant="subtle" (click)="cancel.emit()" i18n="@@aanvraagBlock.annuleren">Annuleren</app-button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,40 +1,49 @@
|
||||
import { Component, input, output } from '@angular/core';
|
||||
import { NgTemplateOutlet } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
/** Molecule: one choice in a CIBG Huisstijl "keuzelijst" — `<li><a class="keuzelijst__link">`
|
||||
/** Molecule: one choice in a CIBG Huisstijl "keuzelijst" — `<li><div class="keuzelijst__link">`
|
||||
with a title and optional instructions (see choice-list.component.ts). Renders a
|
||||
plain (non-interactive) block when there's nothing to navigate to.
|
||||
|
||||
The title is a Bootstrap "stretched-link" (`.stretched-link`, vendored) rather than
|
||||
the whole box being an `<a>`: a `[choiceActions]` slot needs to project a sibling
|
||||
action (e.g. "Annuleren") *inside* the same card, and a `<button>` can't nest
|
||||
inside an `<a>` (invalid HTML, broken a11y). stretched-link keeps the entire card
|
||||
clickable via its `::after` overlay; the projected action sits above that overlay
|
||||
(see its own `position:relative;z-index:2` at the call site) so it stays clickable.
|
||||
|
||||
Unlike the "aanvragen" pattern's `.applications li a::after` (scoped to the `a`
|
||||
tag), CIBG's `.keuzelijst__link:after`/`:hover`/`:focus` rules key off the bare
|
||||
class — keuzelijst assumes every item IS a link — so a non-anchor row would
|
||||
class — keuzelijst assumes every item IS a link — so a non-interactive row would
|
||||
otherwise inherit the chevron and hover accent too. The `--static` modifier below
|
||||
suppresses both for the non-interactive case. A `[choiceActions]` slot projects a
|
||||
sibling action (e.g. "Annuleren") after the anchor — a button can't nest inside it. */
|
||||
suppresses both for that case. `:focus-within` restores the focus accent that
|
||||
`:focus` would have given the (no longer directly focused) card. */
|
||||
@Component({
|
||||
selector: 'app-choice-link',
|
||||
imports: [RouterLink, NgTemplateOutlet],
|
||||
imports: [RouterLink],
|
||||
styles: [`
|
||||
:host{display:contents}
|
||||
.keuzelijst__link{position:relative}
|
||||
.keuzelijst__link:focus-within{background-color:var(--rhc-color-cool-grey-100);box-shadow:inset 4px 0 0 0 var(--rhc-color-lintblauw-500)}
|
||||
.keuzelijst__link--static::after{content:none}
|
||||
.keuzelijst__link--static:hover,.keuzelijst__link--static:focus{background-color:var(--rhc-color-cool-grey-200);box-shadow:none}
|
||||
.keuzelijst__link--static:hover{background-color:var(--rhc-color-cool-grey-200);box-shadow:none}
|
||||
`],
|
||||
template: `
|
||||
<li class="keuzelijst__list-item">
|
||||
@if (to()) {
|
||||
<a class="keuzelijst__link" [routerLink]="to()"><ng-container [ngTemplateOutlet]="body" /></a>
|
||||
} @else if (clickable()) {
|
||||
<a href="#" class="keuzelijst__link" (click)="onActivate($event)"><ng-container [ngTemplateOutlet]="body" /></a>
|
||||
} @else {
|
||||
<div class="keuzelijst__link keuzelijst__link--static"><ng-container [ngTemplateOutlet]="body" /></div>
|
||||
}
|
||||
<ng-content select="[choiceActions]" />
|
||||
<div class="keuzelijst__link" [class.keuzelijst__link--static]="!to() && !clickable()">
|
||||
<h3 class="keuzelijst__header">
|
||||
@if (to()) {
|
||||
<a class="stretched-link" [routerLink]="to()">{{ heading() }}</a>
|
||||
} @else if (clickable()) {
|
||||
<a class="stretched-link" href="#" (click)="onActivate($event)">{{ heading() }}</a>
|
||||
} @else {
|
||||
{{ heading() }}
|
||||
}
|
||||
</h3>
|
||||
@if (instructions()) { <p class="keuzelijst__instructions">{{ instructions() }}</p> }
|
||||
<ng-content select="[choiceActions]" />
|
||||
</div>
|
||||
</li>
|
||||
<ng-template #body>
|
||||
<h3 class="keuzelijst__header">{{ heading() }}</h3>
|
||||
@if (instructions()) { <p class="keuzelijst__instructions">{{ instructions() }}</p> }
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
export class ChoiceLinkComponent {
|
||||
@@ -43,7 +52,7 @@ export class ChoiceLinkComponent {
|
||||
/** Set for a plain routerLink navigation. */
|
||||
to = input('');
|
||||
/** Set when the choice navigates imperatively (e.g. resume with query params) — the
|
||||
row still renders as a clickable `<a>`, but `activate` decides what happens. */
|
||||
row still renders as a clickable card, but `activate` decides what happens. */
|
||||
clickable = input(false);
|
||||
activate = output<void>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user