feat(fp): WP-14 — Storybook taxonomy reorg + Layers MDX
Retitle all 49 stories into a sidebar that makes the DDD seam visible: Foundations (curriculum) -> Design System (Atoms/Molecules/Organisms/ Templates/Devtools, everything in shared/ui + shared/layout) -> Domein (Registratie/Herregistratie/Auth/Brief, everything in a context's ui/). Pin the order via storySort. Add layers.mdx explaining the split and linking the enforcing eslint rules; document the story-title convention in CLAUDE.md. Fix a stale "status banner" reference in atomic-design.mdx left over from WP-13's upload-status-banner deletion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,18 @@ const preview: Preview = {
|
|||||||
date: /Date$/i,
|
date: /Date$/i,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Sidebar tells the DDD seam: reusable design system, then domain contexts.
|
||||||
|
// See src/docs/layers.mdx.
|
||||||
|
options: {
|
||||||
|
storySort: {
|
||||||
|
order: [
|
||||||
|
'Foundations',
|
||||||
|
'Design System',
|
||||||
|
['Atoms', 'Molecules', 'Organisms', 'Templates', 'Devtools'],
|
||||||
|
'Domein',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -120,8 +120,12 @@ but the FE doesn't call them.
|
|||||||
Vitest. Co-locate `*.spec.ts` next to the unit. **Domain and pure logic must have a
|
Vitest. Co-locate `*.spec.ts` next to the unit. **Domain and pure logic must have a
|
||||||
spec** (reducers, combinators, `visibleSteps`, parsers, boundary `parse*` adapters).
|
spec** (reducers, combinators, `visibleSteps`, parsers, boundary `parse*` adapters).
|
||||||
Test the pure function directly — no Angular TestBed for domain. UI is exercised via
|
Test the pure function directly — no Angular TestBed for domain. UI is exercised via
|
||||||
Storybook stories (`*.stories.ts` co-located, titled `Layer/Name`, a11y addon on),
|
Storybook stories (`*.stories.ts` co-located, a11y addon on), not heavy component tests.
|
||||||
not heavy component tests.
|
**Story titles mirror the sidebar's Design System/Domein split** (see
|
||||||
|
`src/docs/layers.mdx`): a `shared/ui`/`shared/layout` component is titled
|
||||||
|
`Design System/<Atoms|Molecules|Organisms|Templates|Devtools>/<Name>`; a component in a
|
||||||
|
context's `ui/` is titled `Domein/<Context>/<Name>` — full stop, regardless of which
|
||||||
|
atomic layer it is (a context organism doesn't get its own `Organisms/` bucket).
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ for its existing violations, so every WP ends green.
|
|||||||
| [WP-11](WP-11-markup-fidelity.md) | CIBG markup fidelity: application-link + absent-class triage | 2 · CIBG | done |
|
| [WP-11](WP-11-markup-fidelity.md) | CIBG markup fidelity: application-link + absent-class triage | 2 · CIBG | done |
|
||||||
| [WP-12](WP-12-datablock.md) | CIBG Datablock for application data | 2 · CIBG | done |
|
| [WP-12](WP-12-datablock.md) | CIBG Datablock for application data | 2 · CIBG | done |
|
||||||
| [WP-13](WP-13-cibg-gap-register.md) | CIBG-gap register + hygiene + MDX | 2 · CIBG | done |
|
| [WP-13](WP-13-cibg-gap-register.md) | CIBG-gap register + hygiene + MDX | 2 · CIBG | done |
|
||||||
| [WP-14](WP-14-storybook-taxonomy.md) | Storybook taxonomy reorg + Layers MDX | 3 · Storybook | todo |
|
| [WP-14](WP-14-storybook-taxonomy.md) | Storybook taxonomy reorg + Layers MDX | 3 · Storybook | done |
|
||||||
| [WP-15](WP-15-missing-stories.md) | Missing stories: shell + brief components | 3 · Storybook | todo |
|
| [WP-15](WP-15-missing-stories.md) | Missing stories: shell + brief components | 3 · Storybook | todo |
|
||||||
| [WP-16](WP-16-component-a11y.md) | Component a11y: description wiring + alert role | 4 · a11y | todo |
|
| [WP-16](WP-16-component-a11y.md) | Component a11y: description wiring + alert role | 4 · a11y | todo |
|
||||||
| [WP-17](WP-17-app-a11y.md) | App-level a11y: route focus, template lint, WCAG checklist | 4 · a11y | todo |
|
| [WP-17](WP-17-app-a11y.md) | App-level a11y: route focus, template lint, WCAG checklist | 4 · a11y | todo |
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
# WP-14 — Storybook taxonomy reorg + Layers MDX
|
# WP-14 — Storybook taxonomy reorg + Layers MDX
|
||||||
|
|
||||||
Status: todo
|
Status: done (<pending commit>)
|
||||||
Phase: 3 — Storybook as curriculum
|
Phase: 3 — Storybook as curriculum
|
||||||
|
|
||||||
|
> **Deviation:** the "Layout/" bucket (breadcrumb, site-footer, site-header) wasn't in the
|
||||||
|
> Decisions block's explicit scheme, so each got folded into Atoms/Molecules/Organisms by
|
||||||
|
> its own doc-comment classification (breadcrumb → Molecules, site-footer/site-header →
|
||||||
|
> Organisms, both already documented as such in their component header comments) rather
|
||||||
|
> than kept as a separate bucket. Fixing `atomic-design.mdx`'s "status banner" reference
|
||||||
|
> (stale since WP-13 deleted `upload-status-banner`) was caught as a side effect of
|
||||||
|
> reviewing every MDX page for broken references — not itself a retitle issue, but the
|
||||||
|
> same "unbroken MDX" acceptance criterion covers it.
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
|
|
||||||
49 stories sit in a flat `Atoms/Molecules/Organisms/Templates/Layout` scheme with domain
|
49 stories sit in a flat `Atoms/Molecules/Organisms/Templates/Layout` scheme with domain
|
||||||
@@ -58,11 +67,11 @@ Domein/
|
|||||||
|
|
||||||
## Acceptance criteria
|
## Acceptance criteria
|
||||||
|
|
||||||
- [ ] Sidebar shows exactly Foundations → Design System → Domein with the sub-order
|
- [x] Sidebar shows exactly Foundations → Design System → Domein with the sub-order
|
||||||
pinned.
|
pinned.
|
||||||
- [ ] Zero story titles outside the scheme (grep `title:` and eyeball).
|
- [x] Zero story titles outside the scheme (grep `title:` and eyeball).
|
||||||
- [ ] `layers.mdx` renders; existing MDX pages unbroken.
|
- [x] `layers.mdx` renders; existing MDX pages unbroken.
|
||||||
- [ ] Convention in CLAUDE.md.
|
- [x] Convention in CLAUDE.md.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
|
|||||||
2822
documentation.json
2822
documentation.json
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { LoginFormComponent } from './login-form.component';
|
import { LoginFormComponent } from './login-form.component';
|
||||||
|
|
||||||
const meta: Meta<LoginFormComponent> = {
|
const meta: Meta<LoginFormComponent> = {
|
||||||
title: 'Organisms/Login Form',
|
title: 'Domein/Auth/Login Form',
|
||||||
component: LoginFormComponent,
|
component: LoginFormComponent,
|
||||||
};
|
};
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ const render = (b: Brief, decisions: BriefDecisions) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const meta: Meta<LetterComposerComponent> = {
|
const meta: Meta<LetterComposerComponent> = {
|
||||||
title: 'Organisms/Letter Composer',
|
title: 'Domein/Brief/Letter Composer',
|
||||||
component: LetterComposerComponent,
|
component: LetterComposerComponent,
|
||||||
};
|
};
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Uren } from '@registratie/domain/value-objects/uren';
|
|||||||
const validData = { uren: 4160 as Uren, jaren: 5, punten: 200, documents: [] };
|
const validData = { uren: 4160 as Uren, jaren: 5, punten: 200, documents: [] };
|
||||||
|
|
||||||
const meta: Meta<HerregistratieWizardComponent> = {
|
const meta: Meta<HerregistratieWizardComponent> = {
|
||||||
title: 'Herregistratie/Wizard',
|
title: 'Domein/Herregistratie/Wizard',
|
||||||
component: HerregistratieWizardComponent,
|
component: HerregistratieWizardComponent,
|
||||||
// The wizard injects BigProfileStore (for the optimistic cross-page flag),
|
// The wizard injects BigProfileStore (for the optimistic cross-page flag),
|
||||||
// which creates httpResources — so the story needs an HttpClient.
|
// which creates httpResources — so the story needs an HttpClient.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Uren } from '@registratie/domain/value-objects/uren';
|
|||||||
const validData = { werktBuitenland: false, uren: 4160 as Uren, punten: 200 as Uren };
|
const validData = { werktBuitenland: false, uren: 4160 as Uren, punten: 200 as Uren };
|
||||||
|
|
||||||
const meta: Meta<IntakeWizardComponent> = {
|
const meta: Meta<IntakeWizardComponent> = {
|
||||||
title: 'Herregistratie/IntakeWizard',
|
title: 'Domein/Herregistratie/IntakeWizard',
|
||||||
component: IntakeWizardComponent,
|
component: IntakeWizardComponent,
|
||||||
// Injects BigProfileStore (optimistic flag) which creates httpResources.
|
// Injects BigProfileStore (optimistic flag) which creates httpResources.
|
||||||
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const base = {
|
|||||||
} satisfies Omit<Aanvraag, 'status'>;
|
} satisfies Omit<Aanvraag, 'status'>;
|
||||||
|
|
||||||
const meta: Meta<AanvraagBlockComponent> = {
|
const meta: Meta<AanvraagBlockComponent> = {
|
||||||
title: 'Organisms/Aanvraag Block',
|
title: 'Domein/Registratie/Aanvraag Block',
|
||||||
component: AanvraagBlockComponent,
|
component: AanvraagBlockComponent,
|
||||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { moduleMetadata } from '@storybook/angular';
|
|||||||
import { AddressFieldsComponent } from './address-fields.component';
|
import { AddressFieldsComponent } from './address-fields.component';
|
||||||
|
|
||||||
const meta: Meta<AddressFieldsComponent> = {
|
const meta: Meta<AddressFieldsComponent> = {
|
||||||
title: 'Organisms/Address Fields',
|
title: 'Domein/Registratie/Address Fields',
|
||||||
component: AddressFieldsComponent,
|
component: AddressFieldsComponent,
|
||||||
decorators: [moduleMetadata({ imports: [AddressFieldsComponent] })],
|
decorators: [moduleMetadata({ imports: [AddressFieldsComponent] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const validData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<ChangeRequestFormComponent> = {
|
const meta: Meta<ChangeRequestFormComponent> = {
|
||||||
title: 'Organisms/Change Request Form',
|
title: 'Domein/Registratie/Change Request Form',
|
||||||
component: ChangeRequestFormComponent,
|
component: ChangeRequestFormComponent,
|
||||||
// The form injects ApiClient (over HttpClient) for the submit command.
|
// The form injects ApiClient (over HttpClient) for the submit command.
|
||||||
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const validData: ValidRegistratie = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<RegistratieWizardComponent> = {
|
const meta: Meta<RegistratieWizardComponent> = {
|
||||||
title: 'Registratie/RegistratieWizard',
|
title: 'Domein/Registratie/RegistratieWizard',
|
||||||
component: RegistratieWizardComponent,
|
component: RegistratieWizardComponent,
|
||||||
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const base = {
|
|||||||
} satisfies Omit<Registration, 'status'>;
|
} satisfies Omit<Registration, 'status'>;
|
||||||
|
|
||||||
const meta: Meta<RegistrationSummaryComponent> = {
|
const meta: Meta<RegistrationSummaryComponent> = {
|
||||||
title: 'Organisms/Registration Summary',
|
title: 'Domein/Registratie/Registration Summary',
|
||||||
component: RegistrationSummaryComponent,
|
component: RegistrationSummaryComponent,
|
||||||
};
|
};
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { RegistrationTableComponent } from './registration-table.component';
|
import { RegistrationTableComponent } from './registration-table.component';
|
||||||
|
|
||||||
const meta: Meta<RegistrationTableComponent> = {
|
const meta: Meta<RegistrationTableComponent> = {
|
||||||
title: 'Organisms/Registration Table',
|
title: 'Domein/Registratie/Registration Table',
|
||||||
component: RegistrationTableComponent,
|
component: RegistrationTableComponent,
|
||||||
};
|
};
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { provideRouter } from '@angular/router';
|
|||||||
import { BreadcrumbComponent } from './breadcrumb.component';
|
import { BreadcrumbComponent } from './breadcrumb.component';
|
||||||
|
|
||||||
const meta: Meta<BreadcrumbComponent> = {
|
const meta: Meta<BreadcrumbComponent> = {
|
||||||
title: 'Layout/Breadcrumb',
|
title: 'Design System/Molecules/Breadcrumb',
|
||||||
component: BreadcrumbComponent,
|
component: BreadcrumbComponent,
|
||||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { PageShellComponent } from './page-shell.component';
|
|||||||
import { ButtonComponent } from '@shared/ui/button/button.component';
|
import { ButtonComponent } from '@shared/ui/button/button.component';
|
||||||
|
|
||||||
const meta: Meta<PageShellComponent> = {
|
const meta: Meta<PageShellComponent> = {
|
||||||
title: 'Templates/PageShell',
|
title: 'Design System/Templates/PageShell',
|
||||||
component: PageShellComponent,
|
component: PageShellComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
applicationConfig({ providers: [provideRouter([])] }),
|
applicationConfig({ providers: [provideRouter([])] }),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { SiteFooterComponent } from './site-footer.component';
|
import { SiteFooterComponent } from './site-footer.component';
|
||||||
|
|
||||||
const meta: Meta<SiteFooterComponent> = {
|
const meta: Meta<SiteFooterComponent> = {
|
||||||
title: 'Layout/Site Footer',
|
title: 'Design System/Organisms/Site Footer',
|
||||||
component: SiteFooterComponent,
|
component: SiteFooterComponent,
|
||||||
render: () => ({ template: `<app-site-footer />` }),
|
render: () => ({ template: `<app-site-footer />` }),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { provideRouter } from '@angular/router';
|
|||||||
import { SiteHeaderComponent } from './site-header.component';
|
import { SiteHeaderComponent } from './site-header.component';
|
||||||
|
|
||||||
const meta: Meta<SiteHeaderComponent> = {
|
const meta: Meta<SiteHeaderComponent> = {
|
||||||
title: 'Layout/Site Header',
|
title: 'Design System/Organisms/Site Header',
|
||||||
component: SiteHeaderComponent,
|
component: SiteHeaderComponent,
|
||||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { WizardShellComponent } from './wizard-shell.component';
|
import { WizardShellComponent } from './wizard-shell.component';
|
||||||
|
|
||||||
const meta: Meta<WizardShellComponent> = {
|
const meta: Meta<WizardShellComponent> = {
|
||||||
title: 'Templates/WizardShell',
|
title: 'Design System/Templates/WizardShell',
|
||||||
component: WizardShellComponent,
|
component: WizardShellComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { AlertComponent } from './alert.component';
|
import { AlertComponent } from './alert.component';
|
||||||
|
|
||||||
const meta: Meta<AlertComponent> = {
|
const meta: Meta<AlertComponent> = {
|
||||||
title: 'Atoms/Alert',
|
title: 'Design System/Atoms/Alert',
|
||||||
component: AlertComponent,
|
component: AlertComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { provideRouter } from '@angular/router';
|
|||||||
import { ApplicationLinkComponent } from './application-link.component';
|
import { ApplicationLinkComponent } from './application-link.component';
|
||||||
|
|
||||||
const meta: Meta<ApplicationLinkComponent> = {
|
const meta: Meta<ApplicationLinkComponent> = {
|
||||||
title: 'Molecules/Application Link',
|
title: 'Design System/Molecules/Application Link',
|
||||||
component: ApplicationLinkComponent,
|
component: ApplicationLinkComponent,
|
||||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ApplicationListComponent } from './application-list.component';
|
|||||||
import { ApplicationLinkComponent } from '@shared/ui/application-link/application-link.component';
|
import { ApplicationLinkComponent } from '@shared/ui/application-link/application-link.component';
|
||||||
|
|
||||||
const meta: Meta<ApplicationListComponent> = {
|
const meta: Meta<ApplicationListComponent> = {
|
||||||
title: 'Molecules/Application List',
|
title: 'Design System/Molecules/Application List',
|
||||||
component: ApplicationListComponent,
|
component: ApplicationListComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
applicationConfig({ providers: [provideRouter([])] }),
|
applicationConfig({ providers: [provideRouter([])] }),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
title: 'Molecules/Async States',
|
title: 'Design System/Molecules/Async States',
|
||||||
decorators: [moduleMetadata({ imports: [...ASYNC, SkeletonComponent] })],
|
decorators: [moduleMetadata({ imports: [...ASYNC, SkeletonComponent] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
// isEmpty is a function — Storybook strips function args, so set it here.
|
// isEmpty is a function — Storybook strips function args, so set it here.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { ButtonComponent } from './button.component';
|
import { ButtonComponent } from './button.component';
|
||||||
|
|
||||||
const meta: Meta<ButtonComponent> = {
|
const meta: Meta<ButtonComponent> = {
|
||||||
title: 'Atoms/Button',
|
title: 'Design System/Atoms/Button',
|
||||||
component: ButtonComponent,
|
component: ButtonComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { CardComponent } from './card.component';
|
import { CardComponent } from './card.component';
|
||||||
|
|
||||||
const meta: Meta<CardComponent> = {
|
const meta: Meta<CardComponent> = {
|
||||||
title: 'Molecules/Card',
|
title: 'Design System/Molecules/Card',
|
||||||
component: CardComponent,
|
component: CardComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { CheckboxComponent } from './checkbox.component';
|
import { CheckboxComponent } from './checkbox.component';
|
||||||
|
|
||||||
const meta: Meta<CheckboxComponent> = {
|
const meta: Meta<CheckboxComponent> = {
|
||||||
title: 'Atoms/Checkbox',
|
title: 'Design System/Atoms/Checkbox',
|
||||||
component: CheckboxComponent,
|
component: CheckboxComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { provideRouter } from '@angular/router';
|
|||||||
import { ChoiceLinkComponent } from './choice-link.component';
|
import { ChoiceLinkComponent } from './choice-link.component';
|
||||||
|
|
||||||
const meta: Meta<ChoiceLinkComponent> = {
|
const meta: Meta<ChoiceLinkComponent> = {
|
||||||
title: 'Molecules/Choice Link',
|
title: 'Design System/Molecules/Choice Link',
|
||||||
component: ChoiceLinkComponent,
|
component: ChoiceLinkComponent,
|
||||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ChoiceListComponent } from './choice-list.component';
|
|||||||
import { ChoiceLinkComponent } from '@shared/ui/choice-link/choice-link.component';
|
import { ChoiceLinkComponent } from '@shared/ui/choice-link/choice-link.component';
|
||||||
|
|
||||||
const meta: Meta<ChoiceListComponent> = {
|
const meta: Meta<ChoiceListComponent> = {
|
||||||
title: 'Molecules/Choice List',
|
title: 'Design System/Molecules/Choice List',
|
||||||
component: ChoiceListComponent,
|
component: ChoiceListComponent,
|
||||||
decorators: [
|
decorators: [
|
||||||
applicationConfig({ providers: [provideRouter([])] }),
|
applicationConfig({ providers: [provideRouter([])] }),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { ConfirmationComponent } from './confirmation.component';
|
import { ConfirmationComponent } from './confirmation.component';
|
||||||
|
|
||||||
const meta: Meta<ConfirmationComponent> = {
|
const meta: Meta<ConfirmationComponent> = {
|
||||||
title: 'Molecules/Confirmation',
|
title: 'Design System/Molecules/Confirmation',
|
||||||
component: ConfirmationComponent,
|
component: ConfirmationComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { DataBlockComponent } from './data-block.component';
|
|||||||
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
|
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
|
||||||
|
|
||||||
const meta: Meta<DataBlockComponent> = {
|
const meta: Meta<DataBlockComponent> = {
|
||||||
title: 'Molecules/Data Block',
|
title: 'Design System/Molecules/Data Block',
|
||||||
component: DataBlockComponent,
|
component: DataBlockComponent,
|
||||||
decorators: [moduleMetadata({ imports: [DataRowComponent] })],
|
decorators: [moduleMetadata({ imports: [DataRowComponent] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { DataRowComponent } from './data-row.component';
|
import { DataRowComponent } from './data-row.component';
|
||||||
|
|
||||||
const meta: Meta<DataRowComponent> = {
|
const meta: Meta<DataRowComponent> = {
|
||||||
title: 'Molecules/Data Row',
|
title: 'Design System/Molecules/Data Row',
|
||||||
component: DataRowComponent,
|
component: DataRowComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { DebugStateComponent } from './debug-state.component';
|
|||||||
|
|
||||||
// Devtools, not a design-system atom — titled accordingly.
|
// Devtools, not a design-system atom — titled accordingly.
|
||||||
const meta: Meta<DebugStateComponent> = {
|
const meta: Meta<DebugStateComponent> = {
|
||||||
title: 'Devtools/State debug',
|
title: 'Design System/Devtools/State debug',
|
||||||
component: DebugStateComponent,
|
component: DebugStateComponent,
|
||||||
parameters: {
|
parameters: {
|
||||||
cibgGap: true,
|
cibgGap: true,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { FormFieldComponent } from './form-field.component';
|
|||||||
import { TextInputComponent } from '@shared/ui/text-input/text-input.component';
|
import { TextInputComponent } from '@shared/ui/text-input/text-input.component';
|
||||||
|
|
||||||
const meta: Meta<FormFieldComponent> = {
|
const meta: Meta<FormFieldComponent> = {
|
||||||
title: 'Molecules/Form Field',
|
title: 'Design System/Molecules/Form Field',
|
||||||
component: FormFieldComponent,
|
component: FormFieldComponent,
|
||||||
decorators: [moduleMetadata({ imports: [TextInputComponent] })],
|
decorators: [moduleMetadata({ imports: [TextInputComponent] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { HeadingComponent } from './heading.component';
|
import { HeadingComponent } from './heading.component';
|
||||||
|
|
||||||
const meta: Meta<HeadingComponent> = {
|
const meta: Meta<HeadingComponent> = {
|
||||||
title: 'Atoms/Heading',
|
title: 'Design System/Atoms/Heading',
|
||||||
component: HeadingComponent,
|
component: HeadingComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { provideRouter } from '@angular/router';
|
|||||||
import { LinkComponent } from './link.component';
|
import { LinkComponent } from './link.component';
|
||||||
|
|
||||||
const meta: Meta<LinkComponent> = {
|
const meta: Meta<LinkComponent> = {
|
||||||
title: 'Atoms/Link',
|
title: 'Design System/Atoms/Link',
|
||||||
component: LinkComponent,
|
component: LinkComponent,
|
||||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { PlaceholderChipComponent } from './placeholder-chip.component';
|
import { PlaceholderChipComponent } from './placeholder-chip.component';
|
||||||
|
|
||||||
const meta: Meta<PlaceholderChipComponent> = {
|
const meta: Meta<PlaceholderChipComponent> = {
|
||||||
title: 'Atoms/Placeholder Chip',
|
title: 'Design System/Atoms/Placeholder Chip',
|
||||||
component: PlaceholderChipComponent,
|
component: PlaceholderChipComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { RadioGroupComponent } from './radio-group.component';
|
import { RadioGroupComponent } from './radio-group.component';
|
||||||
|
|
||||||
const meta: Meta<RadioGroupComponent> = {
|
const meta: Meta<RadioGroupComponent> = {
|
||||||
title: 'Atoms/RadioGroup',
|
title: 'Design System/Atoms/RadioGroup',
|
||||||
component: RadioGroupComponent,
|
component: RadioGroupComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ReviewSectionComponent } from './review-section.component';
|
|||||||
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
|
import { DataRowComponent } from '@shared/ui/data-row/data-row.component';
|
||||||
|
|
||||||
const meta: Meta<ReviewSectionComponent> = {
|
const meta: Meta<ReviewSectionComponent> = {
|
||||||
title: 'Molecules/Review Section',
|
title: 'Design System/Molecules/Review Section',
|
||||||
component: ReviewSectionComponent,
|
component: ReviewSectionComponent,
|
||||||
decorators: [moduleMetadata({ imports: [DataRowComponent] })],
|
decorators: [moduleMetadata({ imports: [DataRowComponent] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const placeholders = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const meta: Meta<RichTextEditorComponent> = {
|
const meta: Meta<RichTextEditorComponent> = {
|
||||||
title: 'Molecules/Rich Text Editor',
|
title: 'Design System/Molecules/Rich Text Editor',
|
||||||
component: RichTextEditorComponent,
|
component: RichTextEditorComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { SkeletonComponent } from './skeleton.component';
|
import { SkeletonComponent } from './skeleton.component';
|
||||||
|
|
||||||
const meta: Meta<SkeletonComponent> = {
|
const meta: Meta<SkeletonComponent> = {
|
||||||
title: 'Atoms/Skeleton',
|
title: 'Design System/Atoms/Skeleton',
|
||||||
component: SkeletonComponent,
|
component: SkeletonComponent,
|
||||||
args: { delay: 0 },
|
args: { delay: 0 },
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { SpinnerComponent } from './spinner.component';
|
import { SpinnerComponent } from './spinner.component';
|
||||||
|
|
||||||
const meta: Meta<SpinnerComponent> = {
|
const meta: Meta<SpinnerComponent> = {
|
||||||
title: 'Atoms/Spinner',
|
title: 'Design System/Atoms/Spinner',
|
||||||
component: SpinnerComponent,
|
component: SpinnerComponent,
|
||||||
parameters: {
|
parameters: {
|
||||||
cibgGap: true,
|
cibgGap: true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { StatusBadgeComponent } from './status-badge.component';
|
import { StatusBadgeComponent } from './status-badge.component';
|
||||||
|
|
||||||
const meta: Meta<StatusBadgeComponent> = {
|
const meta: Meta<StatusBadgeComponent> = {
|
||||||
title: 'Shared UI/Status Badge',
|
title: 'Design System/Atoms/Status Badge',
|
||||||
component: StatusBadgeComponent,
|
component: StatusBadgeComponent,
|
||||||
parameters: {
|
parameters: {
|
||||||
cibgGap: true,
|
cibgGap: true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { StepperComponent } from './stepper.component';
|
import { StepperComponent } from './stepper.component';
|
||||||
|
|
||||||
const meta: Meta<StepperComponent> = {
|
const meta: Meta<StepperComponent> = {
|
||||||
title: 'Molecules/Stepper',
|
title: 'Design System/Molecules/Stepper',
|
||||||
component: StepperComponent,
|
component: StepperComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { provideRouter } from '@angular/router';
|
|||||||
import { TaskListComponent } from './task-list.component';
|
import { TaskListComponent } from './task-list.component';
|
||||||
|
|
||||||
const meta: Meta<TaskListComponent> = {
|
const meta: Meta<TaskListComponent> = {
|
||||||
title: 'Molecules/Task List',
|
title: 'Design System/Molecules/Task List',
|
||||||
component: TaskListComponent,
|
component: TaskListComponent,
|
||||||
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
decorators: [applicationConfig({ providers: [provideRouter([])] })],
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { TextInputComponent } from './text-input.component';
|
import { TextInputComponent } from './text-input.component';
|
||||||
|
|
||||||
const meta: Meta<TextInputComponent> = {
|
const meta: Meta<TextInputComponent> = {
|
||||||
title: 'Atoms/Text Input',
|
title: 'Design System/Atoms/Text Input',
|
||||||
component: TextInputComponent,
|
component: TextInputComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { DeliveryChannelToggleComponent } from './delivery-channel-toggle.component';
|
import { DeliveryChannelToggleComponent } from './delivery-channel-toggle.component';
|
||||||
|
|
||||||
const meta: Meta<DeliveryChannelToggleComponent> = {
|
const meta: Meta<DeliveryChannelToggleComponent> = {
|
||||||
title: 'Atoms/DeliveryChannelToggle',
|
title: 'Design System/Atoms/DeliveryChannelToggle',
|
||||||
component: DeliveryChannelToggleComponent,
|
component: DeliveryChannelToggleComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { DocumentCategory, Upload } from '@shared/upload/upload.machine';
|
|||||||
import { DocumentCategoryComponent } from './document-category.component';
|
import { DocumentCategoryComponent } from './document-category.component';
|
||||||
|
|
||||||
const meta: Meta<DocumentCategoryComponent> = {
|
const meta: Meta<DocumentCategoryComponent> = {
|
||||||
title: 'Organisms/DocumentCategory',
|
title: 'Design System/Organisms/DocumentCategory',
|
||||||
component: DocumentCategoryComponent,
|
component: DocumentCategoryComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { UploadStatus } from '@shared/upload/upload.machine';
|
|||||||
import { DocumentChipComponent } from './document-chip.component';
|
import { DocumentChipComponent } from './document-chip.component';
|
||||||
|
|
||||||
const meta: Meta<DocumentChipComponent> = {
|
const meta: Meta<DocumentChipComponent> = {
|
||||||
title: 'Atoms/DocumentChip',
|
title: 'Design System/Atoms/DocumentChip',
|
||||||
component: DocumentChipComponent,
|
component: DocumentChipComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { UploadState } from '@shared/upload/upload.machine';
|
|||||||
import { DocumentUploadComponent } from './document-upload.component';
|
import { DocumentUploadComponent } from './document-upload.component';
|
||||||
|
|
||||||
const meta: Meta<DocumentUploadComponent> = {
|
const meta: Meta<DocumentUploadComponent> = {
|
||||||
title: 'Organisms/DocumentUpload',
|
title: 'Design System/Organisms/DocumentUpload',
|
||||||
component: DocumentUploadComponent,
|
component: DocumentUploadComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { FileInputComponent } from './file-input.component';
|
import { FileInputComponent } from './file-input.component';
|
||||||
|
|
||||||
const meta: Meta<FileInputComponent> = {
|
const meta: Meta<FileInputComponent> = {
|
||||||
title: 'Atoms/FileInput',
|
title: 'Design System/Atoms/FileInput',
|
||||||
component: FileInputComponent,
|
component: FileInputComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { Upload } from '@shared/upload/upload.machine';
|
|||||||
import { SingleUploadComponent } from './single-upload.component';
|
import { SingleUploadComponent } from './single-upload.component';
|
||||||
|
|
||||||
const meta: Meta<SingleUploadComponent> = {
|
const meta: Meta<SingleUploadComponent> = {
|
||||||
title: 'Molecules/SingleUpload',
|
title: 'Design System/Molecules/SingleUpload',
|
||||||
component: SingleUploadComponent,
|
component: SingleUploadComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { UploadProgressBarComponent } from './upload-progress-bar.component';
|
import { UploadProgressBarComponent } from './upload-progress-bar.component';
|
||||||
|
|
||||||
const meta: Meta<UploadProgressBarComponent> = {
|
const meta: Meta<UploadProgressBarComponent> = {
|
||||||
title: 'Atoms/UploadProgressBar',
|
title: 'Design System/Atoms/UploadProgressBar',
|
||||||
component: UploadProgressBarComponent,
|
component: UploadProgressBarComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { UploadStatusIconComponent } from './upload-status-icon.component';
|
import { UploadStatusIconComponent } from './upload-status-icon.component';
|
||||||
|
|
||||||
const meta: Meta<UploadStatusIconComponent> = {
|
const meta: Meta<UploadStatusIconComponent> = {
|
||||||
title: 'Atoms/UploadStatusIcon',
|
title: 'Design System/Atoms/UploadStatusIcon',
|
||||||
component: UploadStatusIconComponent,
|
component: UploadStatusIconComponent,
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ composes atoms; it adds no new visual primitives of its own.
|
|||||||
|
|
||||||
### Organism — `document-upload`
|
### Organism — `document-upload`
|
||||||
|
|
||||||
`shared/ui/upload/document-upload` composes molecules (a file input, status banner,
|
`shared/ui/upload/document-upload` composes molecules (a file input, alert, progress bar,
|
||||||
progress bar, chips) into a section that owns real upload behaviour.
|
chips) into a section that owns real upload behaviour.
|
||||||
|
|
||||||
<Canvas of={DocumentUploadStories.Default} />
|
<Canvas of={DocumentUploadStories.Default} />
|
||||||
|
|
||||||
|
|||||||
83
src/docs/layers.mdx
Normal file
83
src/docs/layers.mdx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { Meta } from '@storybook/addon-docs/blocks';
|
||||||
|
|
||||||
|
<Meta title="Foundations/Layers & Contexts" />
|
||||||
|
|
||||||
|
# Layers & contexts
|
||||||
|
|
||||||
|
This sidebar is laid out to **be** the architecture, not just document it: **Foundations**
|
||||||
|
(this curriculum) → **Design System** (reusable, domain-free) → **Domein** (the six DDD
|
||||||
|
contexts). If a component lives under a context's `ui/`, it's in Domein; everything else
|
||||||
|
in `shared/ui`/`shared/layout` is Design System. See [Atomic design](?path=/docs/foundations-atomic-design--docs)
|
||||||
|
for the Atoms → Molecules → Organisms → Templates ladder inside Design System.
|
||||||
|
|
||||||
|
## Six contexts, one direction
|
||||||
|
|
||||||
|
```
|
||||||
|
src/app/<context>/<layer>/
|
||||||
|
```
|
||||||
|
|
||||||
|
Contexts: `shared` (the base layer — depends on nothing), `auth`, `registratie`,
|
||||||
|
`herregistratie`, `brief` (letter-composition teaching slice), `showcase` (teaching page,
|
||||||
|
sanctioned to read every context — nothing imports it).
|
||||||
|
|
||||||
|
**Dependencies only point inward and in one declared direction between contexts:**
|
||||||
|
|
||||||
|
```
|
||||||
|
herregistratie → registratie → shared
|
||||||
|
auth → shared
|
||||||
|
brief → shared
|
||||||
|
```
|
||||||
|
|
||||||
|
Never the other way — `registratie` may not import `herregistratie`, and no context but
|
||||||
|
`shared` is imported by everyone.
|
||||||
|
|
||||||
|
## Five layers, one direction
|
||||||
|
|
||||||
|
| Layer | Job | Angular allowed? |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `domain/` | business rules + data types | **No — pure TS.** |
|
||||||
|
| `application/` | coordinate state/tasks (stores, commands) | yes (signals) |
|
||||||
|
| `infrastructure/` | where data comes from (HTTP adapters) | yes (HTTP) |
|
||||||
|
| `contracts/` | wire DTOs (the FE⇄BE seam) | no |
|
||||||
|
| `ui/` | how it looks (components, pages) | yes |
|
||||||
|
|
||||||
|
`ui → application → domain`; `ui`/`layout` never import `infrastructure/` directly — they
|
||||||
|
reach data through an application store or command.
|
||||||
|
|
||||||
|
## This is enforced, not just written down
|
||||||
|
|
||||||
|
`eslint.config.mjs` fails the build on every rule above:
|
||||||
|
|
||||||
|
- `domain/` importing `@angular/*` at all (any context).
|
||||||
|
- `shared/` importing a feature context (`@auth/*`, `@registratie/*`, `@herregistratie/*`,
|
||||||
|
`@brief/*`) — the base layer depends on nothing.
|
||||||
|
- `registratie/` importing `@herregistratie/*`/`@brief/*`, `auth/`/`brief/` importing a
|
||||||
|
sibling context — the cross-context direction above.
|
||||||
|
- `contracts/**` importing **anything** — not Angular, not an alias, not even a relative
|
||||||
|
path (ADR-0001's wire seam has to stay a pure DTO shape).
|
||||||
|
- `ui/**`/`layout/**` importing `*/infrastructure/*` — the anti-corruption boundary
|
||||||
|
(ADR-0001) stays behind a store/command, so a page can never bypass it and hand-recompute
|
||||||
|
a business rule the backend already decided.
|
||||||
|
- The generated `ApiClient` imported as a value outside an `infrastructure/` adapter
|
||||||
|
(type-only DTO imports are exempt — they grant no network access).
|
||||||
|
|
||||||
|
Two components get a documented exemption from the "nothing reaches across" rule:
|
||||||
|
`shared/ui/debug-state` (reads every root store, for the dev-only state panel) and
|
||||||
|
`showcase/` (reads every context, for side-by-side teaching pages). Both exemptions live
|
||||||
|
next to the rule they break, in `eslint.config.mjs`, so they can't rot silently.
|
||||||
|
|
||||||
|
## The English/Dutch seam
|
||||||
|
|
||||||
|
Shared/reusable UI is named in **English** (language-agnostic: `button`, `wizard-shell`);
|
||||||
|
domain contexts are named in **Dutch** (`registratie`, `herregistratie`, `*.machine.ts`).
|
||||||
|
Pick the language by which side of the seam the code is on — it's the same seam this
|
||||||
|
sidebar's Design System/Domein split makes visible.
|
||||||
|
|
||||||
|
## See it in the sidebar
|
||||||
|
|
||||||
|
Compare a Design System primitive with the same shape reused across contexts:
|
||||||
|
|
||||||
|
- [Design System → Molecules → Application Link](?path=/story/design-system-molecules-application-link--navigatie) —
|
||||||
|
domain-free, the caller supplies heading/subtitle/cta.
|
||||||
|
- [Domein → Registratie → Aanvraag Block](?path=/story/domein-registratie-aanvraag-block--concept) —
|
||||||
|
a context-specific organism composed from Design System atoms/molecules.
|
||||||
Reference in New Issue
Block a user