dcr-components: Richtlijnen voor ontwikkelaars ā
Kernfilosofie: Waarom we doen wat we doen ā
Onze leidende principes:
- Duidelijkheid boven slimheid: Code moet makkelijk te lezen en te begrijpen zijn.
- Consistentie is cruciaal: Uniforme code maakt samenwerken vlotter.
- Performantie telt: Onze componenten moeten licht en snel zijn.
- Ontwikkelaarsplezier: We willen dat je graag met
dcr-componentswerkt!
Mappenstructuur ā
- Opbouw van bestanden voor een component:
libs/
āāā dcr-components/
āāā src/
āāā lib/
āāā button/
āāā dcr-button.ts # Componentdefinitie
āāā dcr-button.styles.scss # Styles
āāā dcr-button.styles.ts # Automatisch gegenereerd
āāā dcr-button.test.ts # Unit tests
āāā index.ts # Barrelfile
āāā stories/
āāā dcr-button.stories.ts # Storybook stories
āāā dcr-button.stories.base.ts # Automatisch gegenereerd- Gedeelde logica komt onder _shared
libs/
āāā dcr-components/
āāā src/
āāā lib/
āāā _shared/
āāā aria # A11Y logica
āāā const
āāā docs # oa Gedeelde icons om te gebruiken in stories
āāā events # Logica over event handling
āāā utils
āāā controllers # Gedeelde logica voor gebruik in componentenNaamgevingsconventies ā
Duidelijke namen besparen tijd (en debugging!).
In component ts files ā
| casing | prefix | Voorbeeld | |
|---|---|---|---|
| Class | PascalCase | start met Dcr | DcrButton |
| Tag | kebab-case | start met dcr- | dcr-button |
| Public Variable | camelCase | public label = '' | |
| Private variable | camelCase | start met _ | private _isLoading = false |
| Events | kebab-case | this.dispatchEvent(new CustomEvent('value-change')) |
In component scss/css files ā
| casing | prefix | Voorbeeld | |
|---|---|---|---|
| Class Selector | kebab-case | . | .container |
| Attribute Selector | kebab-case | [] | [range="low"] |
| Public Custom Property | kebab-case | --dcr-comp-<component-name>- | --dcr-comp-badge-bg-color |
| Private Custom Property | kebab-case | --_ | --_error-color |
Anatomie van een dcr-component ā
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import styles from './dcr-component-naam.styles.js';
@customElement('dcr-component-naam')
export class DcrComponentNaam extends LitElement {
static override styles = [
styles,
css`
/* Component-specifieke overrides */
`,
];
@property()
greeting = 'Hallo';
@property()
name = 'Wereld';
override render() {
return html` <p>${this.greeting}, ${this.name}!</p> `;
}
Lifecycle methods, event handlers, private methods etc.
_privateMethod() { ... }
}Volgorde properties ā
Om de structuur van onze componentbestanden consistent en voorspelbaar te houden, hanteren we de volgende vaste volgorde voor de declaratie van types, properties en methods:
Union types: Exporteer union types direct na de import-statements bovenaan het bestand.
staticoverrides:- Plaats
staticoverrides van LitElement-eigenschappen (bv.shadowRootOptions,styles) als eerste in de class.
typescript// Voorbeeld static override shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, delegatesFocus: true, }; static override styles = [styles]; // Waar 'styles' geĆÆmporteerd is- Plaats
Publieke
@propertydeclaraties: Gevolgd door alle met@propertygedecoreerde publieke properties.Publieke getters/setters: Definieer publieke getters en setters na de
@propertydeclaraties.@statedeclaraties: Plaats met@stategedecoreerde private reactive properties hierna.Private instance fields: Declareer alle overige private instance fields (niet-reactive state).
Lifecycle methods: Implementeer lifecycle methods in de exacte volgorde waarin ze door Lit worden aangeroepen: Zie: Lit Lifecycle
constructor()connectedCallback()shouldUpdate(changedProperties)willUpdate(changedProperties)render()firstUpdated(changedProperties)updated(changedProperties)disconnectedCallback()
Publieke methods: Definieer alle overige publieke methods van het component.
Private methods: Implementeer alle private helper methods die geen render-logica bevatten.
Private
_render<Part>()functies: Groepeer alle private methods die delen van de template renderen (bv._renderHeader(),_renderItem(item)) aan het einde van de klasse.
Properties ā
- Gebruik de
@property()decorator voor reactieve properties. - Specifieer
type(bv.Number,Boolean,Array,Object). - Specifieer geen
typevoorString, die is default. - Geef string-properies altijd een expliciete
''bij de declaratie (bv.myString = '';), tenzijundefinedeen expliciet gewenste initiƫle staat is. - Geef boolean-properies altijd een expliciete
falsebij de declaratie (bv.myBool = false;), tenzijundefinedeen expliciet gewenste initiƫle staat is. - Gebruik de non-null assertion operator (
!) alleen als je absoluut zeker bent dat een property op dat punt nietnullofundefinedis en TypeScript dit niet kan afleiden - Gebruik
?in TypeScript-definities (myProp?: string;) voor properties die logischerwijs optioneel zijn voor de componentgebruiker - Gebruik
attribute: falseals de property niet als HTML-attribuut gereflecteerd moet worden. - Overweeg
reflect: truezorgvuldig ā enkel voor properties die echt gereflecteerd moeten worden.
Types / Interfaces ā
Complexe interfaces worden zoveel mogelijk vermeden. HTML-attributen zijn by design string. Hoe minder er geconverteerd moet worden, hoe beter. De complexiteit om iets te stringifyen ligt zoveel mogelijk buiten de verantwoordelijkheid van de component. Eenvoudige types zoals
Number,BooleanenArraykunnen wel worden omgezet. Vermijd echter het gebruik van JSON-configuraties om via attributen door te geven.Voor custom types, gebruik een union type die je in dezelfde file exporteert.
De type wordt gedefinieerd vóór de klasse.
typeScriptexport type MyUnionType = 'this' | 'that'; @customElement('dcr-comp') export class DcrComp { @property() myProp: MyUnionType = 'this'; }
Events ā
Naamgeving:
- Gebruik kebab-case (bv.
value-change). - Gebruik voor het werkwoord in eventnamen de tegenwoordige tijd (onvoltooid tegenwoordige tijd) of de basisvorm van het werkwoord, in lijn met standaard HTML-events.
- Voorbeeld:
select,change,load,open,close,submit.
- Voorbeeld:
- Voor events die procesvoltooiing of het bereiken van een eindstaat signaleren: Gebruik een naam die voltooiing reflecteert (bv. met een voltooid deelwoord, of termen als
end,complete,loaded.- Voorbeeld:
data-loaded,animation-ended,dialog-closed).
- Voorbeeld:
- Gebruik kebab-case (bv.
Versturen:
- Gebruik
this.dispatchEvent(new CustomEvent('jouw-event-naam', options)).
- Gebruik
Data (
detail):- Gebruik de
detailproperty voor event-data. - Definieer een TypeScript-interface voor de structuur van
detail.typescript// Voorbeeld interface export interface DcrItemSelectedEventDetail { id: string; value: unknown; } // Versturen met type new CustomEvent<DcrItemSelectedEventDetail>('dcr-item-selected', { detail: { ... } })
- Gebruik de
Event Opties (
bubbles,composed,cancelable):bubbles: false(standaard): Overweegtruespaarzaam, voor events van algemeen belang voor voorouders.composed: false(standaard): Zet naartrueals het event buiten de Shadow DOM ontvangen moet worden (meestal gewenst).cancelable: false(standaard): Overweegtruealleen als er een duidelijke, annuleerbare standaardactie is.
Her-uitsturen van native events (Re-dispatching) Gebruik de
redispatchEvent(this, event)utility om een native event van een intern element opnieuw uit te sturen vanaf de host van het component, volgens deze regels:A. Wanneer
redispatchEventgebruiken:Als een intern, native event extern relevant is en niet van nature correct door de Shadow DOM-grens bubbelt en/of gecomponeerd wordt (bv.
focus,blur,load, vele media-events).Als je de propagatie van een bubbelend event specifiek wilt controleren (zoals de
redispatchEventutility doet doorstopPropagation()onder bepaalde condities aan te roepen) en een exacte kopie van het event wilt her-uitsturen.
B. Wanneer
redispatchEventNIET gebruiken: - Als het originele native event al van nature correct door de Shadow DOM-grens bubbelt Ʃn gecomponeerd wordt (bv. de meeste UI-events zoalsclickop een standaard element) en je geen specifieke propagatiecontrole of event-modificatie nodig hebt dieredispatchEventbiedt.Documentatie (JSDoc):
- Documenteer met
@fires event-naam - Beschrijving. - Beschrijf de
detailstructuur (verwijs naar de interface).
typescript/** * @fires dcr-item-selected - Item geselecteerd. Detail: `DcrItemSelectedEventDetail`. */- Documenteer met
Rendering ā
- Houd ze beknopt en leesbaar.
- Breek complexe templates op in kleinere, private render methods indien nodig (bv.
_renderHeader(),_renderFooter()).
Styling ā
BEM wordt niet gebruikt.
Shadow DOM & Scoping:
- Styles zijn standaard gescoped dankzij Shadow DOM; maak hier volledig gebruik van.
- Houd classnames zo beknopt mogelijk; ze zijn lokaal binnen het component.
Bestandsstructuur & Generatie:
- Alle component-specifieke styles worden geschreven in
*.styles.scss. - Het corresponderende TypeScript-stijlenbestand (
*.styles.ts) wordt automatisch gegenereerd.
- Alle component-specifieke styles worden geschreven in
CSS Custom Properties (Variabelen):
- Definieer component-specifieke CSS custom properties in
:hostvoor externe aanpasbaarheid, met een fallback naar een design token:var(--dcr-comp-<componentnaam>-<propertynaam>, cds.get('cds.sys.fallback')); - Alleen deze CSS custom properties zijn toegankelijk van buiten de Shadow DOM.
- Definieer component-specifieke CSS custom properties in
Design Tokens (CDS):
- Gebruik altijd design tokens via
@use '@diekeure/cds-tokens' as cds;. - Voorbeeld padding:
padding: cds.get('cds.sys.spacing.3xs'); - Voorbeeld fonts:
@include cds.font('label.large'); - Voorbeeld z-index:
@include cds.zindex('content'); - Volg de design tokens zoals gespecificeerd in de Figma-ontwerpen.
- Gebruik altijd design tokens via
Selectors & Specificiteit:
- Geef de voorkeur aan attribuutselectors op
:hostvoor state-styling (bv.:host([invalid]) { /* styles */ }). - Style de inhoud van slots spaarzaam met
::slotted(<selector>); houd de selectors binnen::slotted()eenvoudig. - Gebruik zo weinig mogelijk
::part().
- Geef de voorkeur aan attribuutselectors op
Layout & Properties:
- Gebruik altijd logical properties (bv.
padding-inline-starti.p.v.padding-left). - Definieer een
display-waarde voor:host(bv.display: block;ofdisplay: inline-flex;). - Pas
box-sizing: border-box;toe waar nodig, bij voorkeur op:host.
- Gebruik altijd logical properties (bv.
Efficiƫntie & Leesbaarheid (SCSS):
- Schrijf beknopte, efficiƫnte SCSS. Elke byte telt.
- Structureer SCSS-bestanden met commentaarblokken voor grote secties.
- Volg de volgorde van elementen in de template waar mogelijk. Plaats uitzonderingen/overrides aan het einde.
- Gebruik
inheritvoor CSS properties waar zinvol.
Performance (Transities & Animaties):
- Test transities en animaties grondig op performantie.
- Specificeer geanimeerde properties expliciet; gebruik nooit
transition: all;. - Voor
box-shadowtransities/animaties: zorg dat het element met debox-shadowop een aparte compositing layer draait (bv. viawill-change: box-shadow;of een minimaletransform) en geen andere geanimeerde content bevat. - Ga spaarzaam om met
transform-operaties.
Focus & Borders:
- Indien een custom focus-ring wordt geĆÆmplementeerd, zet
outline: none;op het gefocuste element. - Geef de voorkeur aan
borderbovenbox-shadowhacks voor het simuleren van randen.
- Indien een custom focus-ring wordt geĆÆmplementeerd, zet
Theming & Interactie met andere componenten:
- Test componenten in light, dark, en high-contrast modes.
- Overschrijf styles van geneste (child)
dcr-componentsuitsluitend via hun geƫxposeerde CSS custom properties.
Codekwaliteit & Standaarden:
- Gebruik nooit
!important. - Gebruik geen utility class names; geef blokken een korte, beschrijvende classnaam.
- Gebruik geen experimentele CSS-properties zonder brede browserondersteuning (verifieer via
caniuse.com). - Deel geen stylesheets tussen componenten, tenzij ze een zeer sterke functionele koppeling hebben (bv. button-varianten die een gemeenschappelijke basis delen).
- Gebruik nooit
- Semantische HTML: Gebruik de juiste HTML-tags (
<button>,<nav>,<input type="checkbox">, etc.). - ARIA-attributen: Gebruik ARIA-attributen waar nodig om extra context te bieden voor hulptechnologieƫn.
- Toetsenbordnavigatie: Zorg ervoor dat alle interactieve elementen focusseerbaar en bedienbaar zijn via het toetsenbord.
Testen ā
Hoewel unit tests momenteel nog niet volledig geĆÆmplementeerd zijn, is het verplicht om componenten grondig te testen. We leggen een sterke nadruk op testen via Storybook voor UI, interactie, toegankelijkheid en visuele regressie.
Storybook als centrale testomgeving:
- Variantendekking: Voor elke variant gedefinieerd in Figma MOET een corresponderende Storybook story aangemaakt worden.
- Property-invloed: Elke wezenlijke UI-verandering die optreedt door het aanpassen van component-properties MOET gedemonstreerd worden in een aparte Storybook story of via controls in bestaande stories.
- Interactietests: Elke nuttige gebruikersinteractie (bv. klikken, typen, hover-effecten die state veranderen) MOET getest worden met een
playfunctie in Storybook. - Toegankelijkheidstests (A11Y): De ingebouwde A11Y-tests in Storybook MOETEN altijd slagen voor alle stories. Geen enkele A11Y-violatie wordt geaccepteerd.
- Visuele Regressietests: Alle stories worden gebruikt voor visuele regressietests via Chromatic. Zorg ervoor dat stories representatief zijn voor alle visuele staten.
Unit Tests:
- Het is een vereiste dat elk component op termijn unit tests krijgt.
- Deze tests moeten focussen op:
- Correcte rendering met verschillende properties.
- Correcte emissie van events.
- Functionele correctheid van property-updates.
- Valideren van interne logica en state-transities.
Testuitvoering:
- Voer alle relevante tests (inclusief Storybook interaction tests) uit met het commando:
npx nx test dcr-components
- Voer alle relevante tests (inclusief Storybook interaction tests) uit met het commando:
Algemene Testprincipes:
- Streef naar een zo hoog mogelijke en zinvolle testdekking. Kwaliteit en relevantie van tests primeren boven louter kwantiteit, maar een brede dekking van functionaliteit en UI-staten is cruciaal.
Formattering & linting ā
- Prettier & ESLint zijn je beste vrienden! We hebben voorgeconfigureerde instellingen.
- Maximale regellengte: 120 karakters
Toegankelijkheid (a11y) ā
Commentaar & documentatie ā
Heldere documentatie is cruciaal. Volg deze richtlijnen voor JSDoc en andere commentaren, geĆÆnspireerd op de structuur van DcrBadge:
Componentklasse JSDoc (boven
@customElement):- Algemene beschrijving: Start met een duidelijke samenvatting van het doel en de functionaliteit van het component.
- Gedetailleerde secties: Gebruik Markdown headings (bv.
## Functionaliteit X) binnen JSDoc voor diepgaandere uitleg over specifieke aspecten of features (zoals## Status Types). - Belangrijke notities: Gebruik iconen ( ā ļø Note:) voor waarschuwingen of essentiĆ«le aandachtspunten.
- Externe links: Integreer links naar Figma, Storybook, of andere relevante documentatie met
{@link https://... Figma Prototype}of{@link https://... Storybook}.
Publieke API JSDoc (Properties & Methods):
@property: Elke publieke property MOET JSDoc hebben die het doel, het effect, en eventuele mogelijke waarden of types beschrijft.typescript/** * The numerical value to display in the badge. * When undefined, no count will be shown. */ @property({ type: Number }) count?: number;- interne properties Gebruik
@internalin de JSDoc om properties als intern te beschrijven. Deze worden uit Storybook documentatie gehouden. - Publieke methods: Elke publieke methode MOET JSDoc hebben die beschrijft wat de methode doet, de parameters (
@param), en de returnwaarde (@returns).
Geƫxporteerde Types (bv.
export type StatusType = ...):- Zorg voor duidelijke, zelfdocumenterende namen. De klasse-JSDoc kan hiernaar verwijzen voor context (zie
## Status Typesin het voorbeeld).
- Zorg voor duidelijke, zelfdocumenterende namen. De klasse-JSDoc kan hiernaar verwijzen voor context (zie
Inline commentaar:
- Gebruik beknopt inline commentaar binnen methodes om alleen complexe, niet-direct evidente logica of belangrijke algoritme-stappen te verduidelijken.
Code organisatie ā
Deze regels zorgen voor een consistente publicatie en een heldere versiegeschiedenis van dcr-components.
Component Export via Barrel Files:
- Elk nieuw component MOET via een
index.ts(barrel file) in zijn eigen directory geƫxporteerd worden. - Vervolgens MOET het component vanuit de
src/index.tshoofd-barrel file geƫxporteerd worden, zodat het correct wordt opgenomen in dedistoutput en als een JS-module beschikbaar is.
- Elk nieuw component MOET via een
package.jsonExports:- Voor elk component MOET een expliciete export-entry worden toegevoegd aan
package.jsononder deexportskey. Volg de onderstaande structuur:json{ "exports": { "./dcr-app-shell.js": { // Gebruik de .js extensie voor de publieke API "types": "./dist/lib/app-shell/dcr-app-shell.d.ts", "import": "./dist/lib/app-shell/dcr-app-shell.js", "default": "./dist/lib/app-shell/dcr-app-shell.js" // Kan hetzelfde zijn als import } // ... andere componenten } } - Vervang
dcr-app-shellen de paden met de correcte namen en locaties voor elk component. De mapnaam onderdist/lib/moet overeenkomen met de componentnaam.
- Voor elk component MOET een expliciete export-entry worden toegevoegd aan
Nieuwe componenten aanmaken: Het create-component script ā
We hebben een script om een nieuw component voor je te genereren!
npx nx run tools:create-component --name mijn-nieuwe-component- Dit script zal de basis mappenstructuur en bestanden voor je aanmaken.
Ontwikkelingsworkflow ā
- Maak een nieuwe branch aan vanuit
develop - Start de Storybook dev server voor componentontwikkeling en -visualisatie:
npx nx serve dcr-components. - Implementeer je component, inclusief styles, stories, en documentatie.
- Testen in
ng-client(indien van toepassing):- Bouw de
dcr-componentsbibliotheek:npx nx build dcr-components - Integreer en test het component binnen de
ng-clientapplicatie om de samenwerking met de host-applicatie te valideren. (npx nx serve ng-client)
- Bouw de
- Voer alle relevante tests uit (Storybook interactie- en A11Y-tests, visuele regressie via Chromatic):
npx nx test dcr-components - Commit je wijzigingen volgens de commit conventies (ƩƩn
featvoor nieuwe componenten,fixvoor bugfixes na release). - Push je wijzigingen en maak een Pull Request.
Commits ā
Nieuwe componenten: Voor elk nieuw toegevoegd component mag er maximaal ƩƩn
featcommit worden gebruikt voor de initiƫle implementatie en integratie. Link via # naar ticketnr.Bugfixes (na eerste release): Na de initiƫle release van een component, MOET elke bugfix in een aparte
fixcommit worden vastgelegd. Link via # naar ticketnr.Documenatie: Bij het updaten van documentatie, MOET er per logische blok een aparte
docscommit worden vastgelegd.Meer over conventional commits op Aan de slag > Gebruik van convetional commits
Releaseproces ā
Tooling ā
Om te releasen volg je deze stappen.
- Check de laatste develop branch uit.
- Maak een nieuwe branch
releasing/<something>. - Voer het releasecommando uit:
npx nx run tools:release. - Nu staat er een
chore(release)commit klaar. - Controleer of de changelog en versionering kloppen in alle projecten. Pas ze aan indien nodig.
- Push de changes.
- Push ook tags naar
origin! - Maak een PR en merge deze in develop.
- Check uit op
origin/release/componentsen zorg dat je de laatste versie hebt. - Voer een GIT rebase uit op
develop. - Push naar
origin/release/components. - Doe hetzelfde (stap 9 t.e.m. 11) voor andere projecten indien nodig (
origin/release/docs,origin/release/tokens)) - De pipeline bouwt, test, maakt een npm package en update de publieke Storybook en/of documentatie website.
- Leun achterover en geniet van alle complimenten die jouw kant op komen.
De grote releasechecklist ā
- [ ] Alle tests slagen.
- [ ] Linting en formatting zijn OK.
- [ ]
CHANGELOG.mdis bijgewerkt. - [ ] Versienummer is correct verhoogd in
package.json. - [ ] Pull Request is gemerged naar
develop. - [ ] Alle changes zijn gepusht op release branches
- [ ] Er is een Git tag aangemaakt op de laatste commit :
git tag dcr-components/vX.Y.Z(en/ofcds-docs/vX.Y.Z,cds-tokens/vX.Y.Z). - [ ] Push ook de tag:
git push --tags. - [ ] Team werd ingelicht over de release via Slack