Skip to content

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-components werkt!

Mappenstructuur ​

  • Opbouw van bestanden voor een component:
bash

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
bash

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 componenten

Naamgevingsconventies ​

Duidelijke namen besparen tijd (en debugging!).

In component ts files ​

casingprefixVoorbeeld
ClassPascalCasestart met Dcr DcrButton
Tagkebab-casestart met dcr-dcr-button
Public VariablecamelCasepublic label = ''
Private variablecamelCasestart met _private _isLoading = false
Eventskebab-casethis.dispatchEvent(new CustomEvent('value-change'))

In component scss/css files ​

casingprefixVoorbeeld
Class Selectorkebab-case..container
Attribute Selectorkebab-case[][range="low"]
Public Custom Propertykebab-case--dcr-comp-<component-name>---dcr-comp-badge-bg-color
Private Custom Propertykebab-case--_--_error-color

Anatomie van een dcr-component ​

typescript
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:

  1. Union types: Exporteer union types direct na de import-statements bovenaan het bestand.

  2. static overrides:

    • Plaats static overrides 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
  3. Publieke @property declaraties: Gevolgd door alle met @property gedecoreerde publieke properties.

  4. Publieke getters/setters: Definieer publieke getters en setters na de @property declaraties.

  5. @state declaraties: Plaats met @state gedecoreerde private reactive properties hierna.

  6. Private instance fields: Declareer alle overige private instance fields (niet-reactive state).

  7. Lifecycle methods: Implementeer lifecycle methods in de exacte volgorde waarin ze door Lit worden aangeroepen: Zie: Lit Lifecycle

    1. constructor()
    2. connectedCallback()
    3. shouldUpdate(changedProperties)
    4. willUpdate(changedProperties)
    5. render()
    6. firstUpdated(changedProperties)
    7. updated(changedProperties)
    8. disconnectedCallback()
  8. Publieke methods: Definieer alle overige publieke methods van het component.

  9. Private methods: Implementeer alle private helper methods die geen render-logica bevatten.

  10. 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 type voor String, die is default.
  • Geef string-properies altijd een expliciete '' bij de declaratie (bv. myString = '';), tenzij undefined een expliciet gewenste initiĆ«le staat is.
  • Geef boolean-properies altijd een expliciete false bij de declaratie (bv. myBool = false;), tenzij undefined een expliciet gewenste initiĆ«le staat is.
  • Gebruik de non-null assertion operator (!) alleen als je absoluut zeker bent dat een property op dat punt niet null of undefined is en TypeScript dit niet kan afleiden
  • Gebruik ? in TypeScript-definities (myProp?: string;) voor properties die logischerwijs optioneel zijn voor de componentgebruiker
  • Gebruik attribute: false als de property niet als HTML-attribuut gereflecteerd moet worden.
  • Overweeg reflect: true zorgvuldig – 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, Boolean en Array kunnen 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.

    typeScript
    export type MyUnionType =  'this' | 'that';
    
    
    @customElement('dcr-comp')
    export class DcrComp {
      @property() myProp: MyUnionType = 'this';
    }

Events ​

  1. 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.
    • 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).
  2. Versturen:

    • Gebruik this.dispatchEvent(new CustomEvent('jouw-event-naam', options)).
  3. Data (detail):

    • Gebruik de detail property 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: { ... } })
  4. Event Opties (bubbles, composed, cancelable):

    • bubbles: false (standaard): Overweeg true spaarzaam, voor events van algemeen belang voor voorouders.

    • composed: false (standaard): Zet naar true als het event buiten de Shadow DOM ontvangen moet worden (meestal gewenst).

    • cancelable: false (standaard): Overweeg true alleen als er een duidelijke, annuleerbare standaardactie is.

    • zie Event compositie en retargeting

  5. 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 redispatchEvent gebruiken:

    • 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 redispatchEvent utility doet door stopPropagation() onder bepaalde condities aan te roepen) en een exacte kopie van het event wilt her-uitsturen.

    • zie Redispatch functie

    B. Wanneer redispatchEvent NIET gebruiken: - Als het originele native event al van nature correct door de Shadow DOM-grens bubbelt Ʃn gecomponeerd wordt (bv. de meeste UI-events zoals click op een standaard element) en je geen specifieke propagatiecontrole of event-modificatie nodig hebt die redispatchEvent biedt.

  6. Documentatie (JSDoc):

    • Documenteer met @fires event-naam - Beschrijving.
    • Beschrijf de detail structuur (verwijs naar de interface).
    typescript
    /**
     * @fires dcr-item-selected - Item geselecteerd. Detail: `DcrItemSelectedEventDetail`.
     */

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.

  1. 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.
  2. Bestandsstructuur & Generatie:

    • Alle component-specifieke styles worden geschreven in *.styles.scss.
    • Het corresponderende TypeScript-stijlenbestand (*.styles.ts) wordt automatisch gegenereerd.
  3. CSS Custom Properties (Variabelen):

    • Definieer component-specifieke CSS custom properties in :host voor 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.
  4. 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.
  5. Selectors & Specificiteit:

    • Geef de voorkeur aan attribuutselectors op :host voor 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().
  6. Layout & Properties:

    • Gebruik altijd logical properties (bv. padding-inline-start i.p.v. padding-left).
    • Definieer een display-waarde voor :host (bv. display: block; of display: inline-flex;).
    • Pas box-sizing: border-box; toe waar nodig, bij voorkeur op :host.
  7. 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 inherit voor CSS properties waar zinvol.
  8. Performance (Transities & Animaties):

    • Test transities en animaties grondig op performantie.
    • Specificeer geanimeerde properties expliciet; gebruik nooit transition: all;.
    • Voor box-shadow transities/animaties: zorg dat het element met de box-shadow op een aparte compositing layer draait (bv. via will-change: box-shadow; of een minimale transform) en geen andere geanimeerde content bevat.
    • Ga spaarzaam om met transform-operaties.
  9. Focus & Borders:

    • Indien een custom focus-ring wordt geĆÆmplementeerd, zet outline: none; op het gefocuste element.
    • Geef de voorkeur aan border boven box-shadow hacks voor het simuleren van randen.
  10. Theming & Interactie met andere componenten:

    • Test componenten in light, dark, en high-contrast modes.
    • Overschrijf styles van geneste (child) dcr-components uitsluitend via hun geĆ«xposeerde CSS custom properties.
  11. 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).
  • 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.

  1. 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 play functie 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.
  2. 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.
  3. Testuitvoering:

    • Voer alle relevante tests (inclusief Storybook interaction tests) uit met het commando: npx nx test dcr-components
  4. 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:

  1. 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}.
  2. 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 @internal in 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).
  3. Geƫxporteerde Types (bv. export type StatusType = ...):

    • Zorg voor duidelijke, zelfdocumenterende namen. De klasse-JSDoc kan hiernaar verwijzen voor context (zie ## Status Types in het voorbeeld).
  4. 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.

  1. 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.ts hoofd-barrel file geĆ«xporteerd worden, zodat het correct wordt opgenomen in de dist output en als een JS-module beschikbaar is.
  2. package.json Exports:

    • Voor elk component MOET een expliciete export-entry worden toegevoegd aan package.json onder de exports key. 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-shell en de paden met de correcte namen en locaties voor elk component. De mapnaam onder dist/lib/ moet overeenkomen met de componentnaam.

Nieuwe componenten aanmaken: Het create-component script ​

We hebben een script om een nieuw component voor je te genereren!

bash
npx nx run tools:create-component --name mijn-nieuwe-component
  • Dit script zal de basis mappenstructuur en bestanden voor je aanmaken.

Ontwikkelingsworkflow ​

  1. Maak een nieuwe branch aan vanuit develop
  2. Start de Storybook dev server voor componentontwikkeling en -visualisatie: npx nx serve dcr-components.
  3. Implementeer je component, inclusief styles, stories, en documentatie.
  4. Testen in ng-client (indien van toepassing):
    • Bouw de dcr-components bibliotheek: npx nx build dcr-components
    • Integreer en test het component binnen de ng-client applicatie om de samenwerking met de host-applicatie te valideren. (npx nx serve ng-client)
  5. Voer alle relevante tests uit (Storybook interactie- en A11Y-tests, visuele regressie via Chromatic): npx nx test dcr-components
  6. Commit je wijzigingen volgens de commit conventies (ƩƩn feat voor nieuwe componenten, fix voor bugfixes na release).
  7. Push je wijzigingen en maak een Pull Request.

Commits ​

  • Nieuwe componenten: Voor elk nieuw toegevoegd component mag er maximaal ƩƩn feat commit 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 fix commit worden vastgelegd. Link via # naar ticketnr.

  • Documenatie: Bij het updaten van documentatie, MOET er per logische blok een aparte docs commit worden vastgelegd.

  • Meer over conventional commits op Aan de slag > Gebruik van convetional commits

Releaseproces ​

Tooling ​

Om te releasen volg je deze stappen.

  1. Check de laatste develop branch uit.
  2. Maak een nieuwe branch releasing/<something>.
  3. Voer het releasecommando uit: npx nx run tools:release.
  4. Nu staat er een chore(release) commit klaar.
  5. Controleer of de changelog en versionering kloppen in alle projecten. Pas ze aan indien nodig.
  6. Push de changes.
  7. Push ook tags naar origin!
  8. Maak een PR en merge deze in develop.
  9. Check uit op origin/release/components en zorg dat je de laatste versie hebt.
  10. Voer een GIT rebase uit op develop.
  11. Push naar origin/release/components.
  12. Doe hetzelfde (stap 9 t.e.m. 11) voor andere projecten indien nodig (origin/release/docs, origin/release/tokens))
  13. De pipeline bouwt, test, maakt een npm package en update de publieke Storybook en/of documentatie website.
  14. Leun achterover en geniet van alle complimenten die jouw kant op komen.

De grote releasechecklist ​

  1. [ ] Alle tests slagen.
  2. [ ] Linting en formatting zijn OK.
  3. [ ] CHANGELOG.md is bijgewerkt.
  4. [ ] Versienummer is correct verhoogd in package.json.
  5. [ ] Pull Request is gemerged naar develop.
  6. [ ] Alle changes zijn gepusht op release branches
  7. [ ] Er is een Git tag aangemaakt op de laatste commit : git tag dcr-components/vX.Y.Z (en/of cds-docs/vX.Y.Z , cds-tokens/vX.Y.Z).
  8. [ ] Push ook de tag: git push --tags.
  9. [ ] Team werd ingelicht over de release via Slack