feat(dashboard): restyle Mijn aanvragen / Wat moet ik regelen as a CIBG keuzelijst
Both sections offer a set of choices the user picks between to proceed, matching
designsystem.cibg.nl/componenten/keuzelijst rather than the "aanvragen" row pattern
("Wat wilt u doen?" keeps that look — it's a static nav list, not a choice list).
- New shared/ui molecules: choice-list (heading + keuzelijst__list, wired via
aria-labelledby per CIBG's a11y guidance) and choice-link (one keuzelijst__link
choice; routerLink, imperative-clickable, or a plain non-interactive block).
- choice-link's non-interactive block needed a `--static` modifier: CIBG's
`.keuzelijst__link:after`/`:hover`/`:focus` key off the bare class (keuzelijst
assumes every item is a link), unlike `.applications li a::after` which is scoped
to the anchor — without it, a non-actionable aanvraag row inherited a chevron and
hover accent it shouldn't have.
- task-list.component.ts now composes choice-list/choice-link internally; public
API unchanged except a new required `listHeading` input (the heading moves inside
the list for the aria-labelledby link, so dashboard.page.ts stops rendering it
separately — same fix applied to "Mijn aanvragen").
- aanvraag-block.component.ts moves from application-link to choice-link, combining
its separate status/subtitle text into one instructions paragraph (keuzelijst has
no cta field — the row itself is the action). Only a resumable Concept renders as
a real choice; InBehandeling/Goedgekeurd/Afgewezen stay non-interactive, unchanged
from before.
Verified: lint/check:tokens/build green, 178 tests pass, build-storybook succeeds,
and manually driven end-to-end (dashboard renders both sections as keuzelijst cards,
confirmed via screenshot that non-actionable rows have no chevron after the fix).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2879
documentation.json
2879
documentation.json
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
|||||||
import { Component, computed, input, output } from '@angular/core';
|
import { Component, computed, input, output } from '@angular/core';
|
||||||
import { ButtonComponent } from '@shared/ui/button/button.component';
|
import { ButtonComponent } from '@shared/ui/button/button.component';
|
||||||
import { ApplicationLinkComponent } from '@shared/ui/application-link/application-link.component';
|
import { ChoiceLinkComponent } from '@shared/ui/choice-link/choice-link.component';
|
||||||
import { Aanvraag, AanvraagType } from '@registratie/domain/aanvraag';
|
import { Aanvraag, AanvraagType } from '@registratie/domain/aanvraag';
|
||||||
import { blockActions } from '@registratie/domain/block-actions';
|
import { blockActions } from '@registratie/domain/block-actions';
|
||||||
|
|
||||||
@@ -11,29 +11,27 @@ const TYPE_LABELS: Record<AanvraagType, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** Organism: one application on the dashboard's "Mijn aanvragen" list, rendered as
|
/** Organism: one application on the dashboard's "Mijn aanvragen" list, rendered as
|
||||||
a CIBG Huisstijl "aanvragen" row (application-link). Which text/actions it shows
|
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
|
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
|
renders. Only a resumable Concept is clickable — a resolved status has nothing
|
||||||
to navigate to, so it renders as a plain (non-anchor) row. */
|
to navigate to, so it renders as a plain (non-anchor) row. */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-aanvraag-block',
|
selector: 'app-aanvraag-block',
|
||||||
imports: [ButtonComponent, ApplicationLinkComponent],
|
imports: [ButtonComponent, ChoiceLinkComponent],
|
||||||
// display:contents — see application-link.component.ts (keeps <li> a direct <ul> child).
|
// display:contents — see choice-link.component.ts (keeps <li> a direct <ul> child).
|
||||||
styles: [`:host{display:contents}`],
|
styles: [`:host{display:contents}`],
|
||||||
template: `
|
template: `
|
||||||
<app-application-link
|
<app-choice-link
|
||||||
[heading]="typeLabel()"
|
[heading]="typeLabel()"
|
||||||
[status]="statusText()"
|
[instructions]="instructions()"
|
||||||
[subtitle]="subtitle()"
|
|
||||||
[cta]="actions().includes('resume') ? verderGaan : ''"
|
|
||||||
[clickable]="actions().includes('resume')"
|
[clickable]="actions().includes('resume')"
|
||||||
(activate)="resume.emit()">
|
(activate)="resume.emit()">
|
||||||
@if (actions().includes('cancel')) {
|
@if (actions().includes('cancel')) {
|
||||||
<div applicationActions>
|
<div choiceActions>
|
||||||
<app-button variant="subtle" (click)="cancel.emit()" i18n="@@aanvraagBlock.annuleren">Annuleren</app-button>
|
<app-button variant="subtle" (click)="cancel.emit()" i18n="@@aanvraagBlock.annuleren">Annuleren</app-button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</app-application-link>
|
</app-choice-link>
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
export class AanvraagBlockComponent {
|
export class AanvraagBlockComponent {
|
||||||
@@ -42,13 +40,11 @@ export class AanvraagBlockComponent {
|
|||||||
resume = output<void>();
|
resume = output<void>();
|
||||||
cancel = output<void>();
|
cancel = output<void>();
|
||||||
|
|
||||||
protected readonly verderGaan = $localize`:@@aanvraagBlock.verderGaan:Verder gaan`;
|
|
||||||
|
|
||||||
protected typeLabel = computed(() => TYPE_LABELS[this.aanvraag().type]);
|
protected typeLabel = computed(() => TYPE_LABELS[this.aanvraag().type]);
|
||||||
protected actions = computed(() => blockActions(this.aanvraag().status));
|
protected actions = computed(() => blockActions(this.aanvraag().status));
|
||||||
|
|
||||||
// Status-tag → row text (the UI's mapping, not a domain rule).
|
// Status-tag → the choice's description line (the UI's mapping, not a domain rule).
|
||||||
protected statusText = computed(() => {
|
private statusText = computed(() => {
|
||||||
const s = this.aanvraag().status;
|
const s = this.aanvraag().status;
|
||||||
switch (s.tag) {
|
switch (s.tag) {
|
||||||
case 'Concept': return $localize`:@@aanvraagBlock.stap:Stap ${s.stepIndex + 1}:index: van ${s.stepCount}:count:`;
|
case 'Concept': return $localize`:@@aanvraagBlock.stap:Stap ${s.stepIndex + 1}:index: van ${s.stepCount}:count:`;
|
||||||
@@ -57,13 +53,19 @@ export class AanvraagBlockComponent {
|
|||||||
case 'Afgewezen': return $localize`:@@aanvraagBlock.afgewezen:Referentie ${s.referentie}:ref:`;
|
case 'Afgewezen': return $localize`:@@aanvraagBlock.afgewezen:Referentie ${s.referentie}:ref:`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Secondary note under the status line, when there's one to show.
|
// Secondary note appended to the status line, when there's one to show.
|
||||||
protected subtitle = computed(() => {
|
private subtitle = computed(() => {
|
||||||
const s = this.aanvraag().status;
|
const s = this.aanvraag().status;
|
||||||
if (s.tag === 'InBehandeling' && s.manual) return $localize`:@@aanvraagBlock.manual:Uw aanvraag wordt handmatig beoordeeld in de backoffice.`;
|
if (s.tag === 'InBehandeling' && s.manual) return $localize`:@@aanvraagBlock.manual:Uw aanvraag wordt handmatig beoordeeld in de backoffice.`;
|
||||||
if (s.tag === 'Afgewezen') return s.reden;
|
if (s.tag === 'Afgewezen') return s.reden;
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
// The keuzelijst has one description paragraph — combine status + secondary note.
|
||||||
|
protected instructions = computed(() => {
|
||||||
|
const status = this.statusText();
|
||||||
|
const sub = this.subtitle();
|
||||||
|
return sub ? `${status} ${sub}` : status;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatNL(iso?: string): string {
|
function formatNL(iso?: string): string {
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ const meta: Meta<AanvraagBlockComponent> = {
|
|||||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
// A row is an <li> — the "applications" list styling needs the real list context.
|
// A row is an <li> — the keuzelijst styling needs the real list context.
|
||||||
template: `<ul class="list-unstyled"><app-aanvraag-block [aanvraag]="aanvraag" /></ul>`,
|
template: `<ul class="keuzelijst__list"><app-aanvraag-block [aanvraag]="aanvraag" /></ul>`,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { CardComponent } from '@shared/ui/card/card.component';
|
|||||||
import { TaskListComponent } from '@shared/ui/task-list/task-list.component';
|
import { TaskListComponent } from '@shared/ui/task-list/task-list.component';
|
||||||
import { ApplicationListComponent } from '@shared/ui/application-list/application-list.component';
|
import { ApplicationListComponent } from '@shared/ui/application-list/application-list.component';
|
||||||
import { ApplicationLinkComponent } from '@shared/ui/application-link/application-link.component';
|
import { ApplicationLinkComponent } from '@shared/ui/application-link/application-link.component';
|
||||||
|
import { ChoiceListComponent } from '@shared/ui/choice-list/choice-list.component';
|
||||||
import { ASYNC } from '@shared/ui/async/async.component';
|
import { ASYNC } from '@shared/ui/async/async.component';
|
||||||
import { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';
|
import { RegistrationSummaryComponent } from '@registratie/ui/registration-summary/registration-summary.component';
|
||||||
import { RegistrationTableComponent } from '@registratie/ui/registration-table/registration-table.component';
|
import { RegistrationTableComponent } from '@registratie/ui/registration-table/registration-table.component';
|
||||||
@@ -25,7 +26,8 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
|
|||||||
selector: 'app-dashboard-page',
|
selector: 'app-dashboard-page',
|
||||||
imports: [
|
imports: [
|
||||||
PageShellComponent, HeadingComponent, AlertComponent, SkeletonComponent,
|
PageShellComponent, HeadingComponent, AlertComponent, SkeletonComponent,
|
||||||
DataRowComponent, CardComponent, TaskListComponent, ApplicationListComponent, ApplicationLinkComponent, ...ASYNC,
|
DataRowComponent, CardComponent, TaskListComponent, ApplicationListComponent, ApplicationLinkComponent,
|
||||||
|
ChoiceListComponent, ...ASYNC,
|
||||||
RegistrationSummaryComponent, RegistrationTableComponent, AanvraagBlockComponent,
|
RegistrationSummaryComponent, RegistrationTableComponent, AanvraagBlockComponent,
|
||||||
],
|
],
|
||||||
template: `
|
template: `
|
||||||
@@ -33,12 +35,11 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
|
|||||||
<div class="app-stack">
|
<div class="app-stack">
|
||||||
@if (aanvragen().length) {
|
@if (aanvragen().length) {
|
||||||
<section>
|
<section>
|
||||||
<app-heading [level]="2" i18n="@@dashboard.mijnAanvragen">Mijn aanvragen</app-heading>
|
<app-choice-list i18n-heading="@@dashboard.mijnAanvragen" heading="Mijn aanvragen" class="app-section">
|
||||||
<app-application-list class="app-section">
|
|
||||||
@for (a of aanvragen(); track a.id) {
|
@for (a of aanvragen(); track a.id) {
|
||||||
<app-aanvraag-block animate.enter="app-item-enter" animate.leave="app-item-leave" [aanvraag]="a" (resume)="resume(a)" (cancel)="cancelAanvraag(a)" />
|
<app-aanvraag-block animate.enter="app-item-enter" animate.leave="app-item-leave" [aanvraag]="a" (resume)="resume(a)" (cancel)="cancelAanvraag(a)" />
|
||||||
}
|
}
|
||||||
</app-application-list>
|
</app-choice-list>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +52,10 @@ import { tasksFromProfile } from '@registratie/domain/tasks';
|
|||||||
@let tasks = tasksFor($any(p).registration);
|
@let tasks = tasksFor($any(p).registration);
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<app-heading [level]="2" i18n="@@dashboard.watMoetIkRegelen">Wat moet ik regelen</app-heading>
|
|
||||||
@if (tasks.length) {
|
@if (tasks.length) {
|
||||||
<app-task-list class="app-section" [tasks]="tasks" />
|
<app-task-list class="app-section" i18n-listHeading="@@dashboard.watMoetIkRegelen" listHeading="Wat moet ik regelen" [tasks]="tasks" />
|
||||||
} @else {
|
} @else {
|
||||||
|
<app-heading [level]="2" i18n="@@dashboard.watMoetIkRegelen">Wat moet ik regelen</app-heading>
|
||||||
<p class="app-text-subtle" i18n="@@dashboard.nietsOpenstaan">U heeft op dit moment niets openstaan.</p>
|
<p class="app-text-subtle" i18n="@@dashboard.nietsOpenstaan">U heeft op dit moment niets openstaan.</p>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
54
src/app/shared/ui/choice-link/choice-link.component.ts
Normal file
54
src/app/shared/ui/choice-link/choice-link.component.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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">`
|
||||||
|
with a title and optional instructions (see choice-list.component.ts). Renders a
|
||||||
|
plain (non-interactive) block when there's nothing to navigate to.
|
||||||
|
|
||||||
|
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
|
||||||
|
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. */
|
||||||
|
@Component({
|
||||||
|
selector: 'app-choice-link',
|
||||||
|
imports: [RouterLink, NgTemplateOutlet],
|
||||||
|
styles: [`
|
||||||
|
:host{display:contents}
|
||||||
|
.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}
|
||||||
|
`],
|
||||||
|
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]" />
|
||||||
|
</li>
|
||||||
|
<ng-template #body>
|
||||||
|
<h3 class="keuzelijst__header">{{ heading() }}</h3>
|
||||||
|
@if (instructions()) { <p class="keuzelijst__instructions">{{ instructions() }}</p> }
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class ChoiceLinkComponent {
|
||||||
|
heading = input.required<string>();
|
||||||
|
instructions = input('');
|
||||||
|
/** 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. */
|
||||||
|
clickable = input(false);
|
||||||
|
activate = output<void>();
|
||||||
|
|
||||||
|
protected onActivate(ev: Event) {
|
||||||
|
ev.preventDefault(); // fragment href resolves against <base href>, not the route
|
||||||
|
this.activate.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/app/shared/ui/choice-link/choice-link.stories.ts
Normal file
27
src/app/shared/ui/choice-link/choice-link.stories.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/angular';
|
||||||
|
import { applicationConfig } from '@storybook/angular';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
import { ChoiceLinkComponent } from './choice-link.component';
|
||||||
|
|
||||||
|
const meta: Meta<ChoiceLinkComponent> = {
|
||||||
|
title: 'Molecules/Choice Link',
|
||||||
|
component: ChoiceLinkComponent,
|
||||||
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
// Rows are <li>s — a real list gives them their normal layout in the story.
|
||||||
|
template: `<ul class="keuzelijst__list"><app-choice-link [heading]="heading" [instructions]="instructions" [to]="to" [clickable]="clickable" /></ul>`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<ChoiceLinkComponent>;
|
||||||
|
|
||||||
|
export const Navigatie: Story = {
|
||||||
|
args: { heading: 'Ik heb een Nederlands diploma', instructions: 'U kunt direct uw registratie aanvragen.', to: '/registreren' },
|
||||||
|
};
|
||||||
|
export const Actie: Story = {
|
||||||
|
args: { heading: 'Inschrijving', instructions: 'Stap 2 van 3', clickable: true },
|
||||||
|
};
|
||||||
|
export const NietInteractief: Story = {
|
||||||
|
args: { heading: 'Herregistratie', instructions: 'Referentie 2024-00123 · ingediend op 12 mei 2024' },
|
||||||
|
};
|
||||||
22
src/app/shared/ui/choice-list/choice-list.component.ts
Normal file
22
src/app/shared/ui/choice-list/choice-list.component.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component, input } from '@angular/core';
|
||||||
|
|
||||||
|
let nextId = 0;
|
||||||
|
|
||||||
|
/** Molecule: the CIBG Huisstijl "keuzelijst" — a heading semantically linked
|
||||||
|
(`aria-labelledby`) to a list of `<app-choice-link>` choices, used where a
|
||||||
|
screen offers a set of options to pick between (see
|
||||||
|
designsystem.cibg.nl/componenten/keuzelijst). Domain-free — the caller
|
||||||
|
supplies the heading text and the choices. */
|
||||||
|
@Component({
|
||||||
|
selector: 'app-choice-list',
|
||||||
|
template: `
|
||||||
|
<h2 class="header header--medium" [id]="headingId">{{ heading() }}</h2>
|
||||||
|
<ul class="keuzelijst__list" [attr.aria-labelledby]="headingId">
|
||||||
|
<ng-content />
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class ChoiceListComponent {
|
||||||
|
heading = input.required<string>();
|
||||||
|
protected readonly headingId = `choice-list-${nextId++}`;
|
||||||
|
}
|
||||||
26
src/app/shared/ui/choice-list/choice-list.stories.ts
Normal file
26
src/app/shared/ui/choice-list/choice-list.stories.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/angular';
|
||||||
|
import { applicationConfig, moduleMetadata } from '@storybook/angular';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
import { ChoiceListComponent } from './choice-list.component';
|
||||||
|
import { ChoiceLinkComponent } from '@shared/ui/choice-link/choice-link.component';
|
||||||
|
|
||||||
|
const meta: Meta<ChoiceListComponent> = {
|
||||||
|
title: 'Molecules/Choice List',
|
||||||
|
component: ChoiceListComponent,
|
||||||
|
decorators: [
|
||||||
|
applicationConfig({ providers: [provideRouter([])] }),
|
||||||
|
moduleMetadata({ imports: [ChoiceLinkComponent] }),
|
||||||
|
],
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<app-choice-list [heading]="heading">
|
||||||
|
<app-choice-link heading="Ik heb een Nederlands diploma" instructions="U kunt direct uw registratie aanvragen." to="/registreren" />
|
||||||
|
<app-choice-link heading="Ik heb een buitenlands diploma" instructions="Uw diploma moet eerst officieel erkend worden." clickable="true" />
|
||||||
|
</app-choice-list>`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<ChoiceListComponent>;
|
||||||
|
|
||||||
|
export const Default: Story = { args: { heading: 'Maak een keuze' } };
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, input } from '@angular/core';
|
import { Component, input } from '@angular/core';
|
||||||
import { LinkComponent } from '@shared/ui/link/link.component';
|
import { ChoiceListComponent } from '@shared/ui/choice-list/choice-list.component';
|
||||||
|
import { ChoiceLinkComponent } from '@shared/ui/choice-link/choice-link.component';
|
||||||
|
|
||||||
/** Presentational task shape — what "Wat moet ik regelen" renders. Domain-free so
|
/** Presentational task shape — what "Wat moet ik regelen" renders. Domain-free so
|
||||||
shared/ stays independent of any context (a context's task type that has these
|
shared/ stays independent of any context (a context's task type that has these
|
||||||
@@ -12,40 +13,21 @@ export interface TaskItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Molecule: the "Wat moet ik regelen" action list (NL Design System "Mijn
|
/** Molecule: the "Wat moet ik regelen" action list (NL Design System "Mijn
|
||||||
omgeving" pattern). Each task is a titled row with a call to action. */
|
omgeving" pattern), rendered as a CIBG Huisstijl "keuzelijst" — each task is a
|
||||||
|
choice the user picks to resolve it. `actionLabel` has no keuzelijst equivalent
|
||||||
|
(the whole row is the action; the chevron already implies "ga verder"). */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-task-list',
|
selector: 'app-task-list',
|
||||||
imports: [LinkComponent],
|
imports: [ChoiceListComponent, ChoiceLinkComponent],
|
||||||
styles: [`
|
|
||||||
:host{display:block}
|
|
||||||
.tasks{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--rhc-space-max-md)}
|
|
||||||
.task{
|
|
||||||
display:flex;flex-wrap:wrap;gap:var(--rhc-space-max-md) var(--rhc-space-max-xl);
|
|
||||||
align-items:center;justify-content:space-between;
|
|
||||||
background:var(--rhc-color-wit);
|
|
||||||
border:var(--rhc-border-width-sm) solid var(--rhc-color-border-subtle);
|
|
||||||
border-inline-start:var(--rhc-border-width-md) solid var(--rhc-color-lintblauw-700);
|
|
||||||
border-radius:var(--rhc-border-radius-md);
|
|
||||||
padding:var(--rhc-space-max-lg) var(--rhc-space-max-xl);
|
|
||||||
}
|
|
||||||
.title{margin:0 0 var(--rhc-space-max-xs);font-weight:var(--rhc-text-font-weight-semi-bold)}
|
|
||||||
.desc{margin:0;color:var(--rhc-color-foreground-subtle);font-size:var(--rhc-text-font-size-sm)}
|
|
||||||
.cta{flex:0 0 auto}
|
|
||||||
`],
|
|
||||||
template: `
|
template: `
|
||||||
<ul class="tasks">
|
<app-choice-list [heading]="listHeading()">
|
||||||
@for (t of tasks(); track t.title) {
|
@for (t of tasks(); track t.title) {
|
||||||
<li class="task">
|
<app-choice-link [heading]="t.title" [instructions]="t.description" [to]="t.to" />
|
||||||
<div>
|
|
||||||
<p class="title">{{ t.title }}</p>
|
|
||||||
<p class="desc">{{ t.description }}</p>
|
|
||||||
</div>
|
|
||||||
<span class="cta"><app-link [to]="t.to">{{ t.actionLabel }} →</app-link></span>
|
|
||||||
</li>
|
|
||||||
}
|
}
|
||||||
</ul>
|
</app-choice-list>
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
export class TaskListComponent {
|
export class TaskListComponent {
|
||||||
|
listHeading = input.required<string>();
|
||||||
tasks = input.required<readonly TaskItem[]>();
|
tasks = input.required<readonly TaskItem[]>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ const meta: Meta<TaskListComponent> = {
|
|||||||
title: 'Molecules/Task List',
|
title: 'Molecules/Task List',
|
||||||
component: TaskListComponent,
|
component: TaskListComponent,
|
||||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
render: (args) => ({ props: args, template: `<app-task-list [tasks]="tasks" />` }),
|
render: (args) => ({ props: args, template: `<app-task-list [listHeading]="listHeading" [tasks]="tasks" />` }),
|
||||||
};
|
};
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<TaskListComponent>;
|
type Story = StoryObj<TaskListComponent>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
|
listHeading: 'Wat moet ik regelen',
|
||||||
tasks: [
|
tasks: [
|
||||||
{ title: 'Vraag uw herregistratie aan', description: 'Verleng uw registratie vóór 31 december 2026.', to: '/herregistratie', actionLabel: 'Herregistratie aanvragen' },
|
{ title: 'Vraag uw herregistratie aan', description: 'Verleng uw registratie vóór 31 december 2026.', to: '/herregistratie', actionLabel: 'Herregistratie aanvragen' },
|
||||||
{ title: 'Controleer uw adresgegevens', description: 'Uw adres is langer dan een jaar niet bevestigd.', to: '/registratie', actionLabel: 'Bekijk uw gegevens' },
|
{ title: 'Controleer uw adresgegevens', description: 'Uw adres is langer dan een jaar niet bevestigd.', to: '/registratie', actionLabel: 'Bekijk uw gegevens' },
|
||||||
|
|||||||
Reference in New Issue
Block a user