@edugouvfr/ngx-dsfr
Version:
NgxDsfr est un portage Angular des éléments d'interface du Système de Design de l'État Français (DSFR).
429 lines (322 loc) • 12.9 kB
Markdown
# @edugouvfr/ngx-dsfr - Guide de contribution
Ce fichier présente les conventions de formalisme et les règles de conception suivies sur ce projet.
- [@edugouvfr/ngx-dsfr - Guide de contribution](#edugouvfrngx-dsfr---guide-de-contribution)
- [Environnement](#environnement)
- [Pré-requis](#pré-requis)
- [Démarrer](#démarrer)
- [Règles de nommage](#règles-de-nommage)
- [Organisation](#organisation)
- [Code style](#code-style)
- [Format messages de commit](#format-messages-de-commit)
- [Format d'entrée du changelog](#format-dentrée-du-changelog)
- [Versionnement](#versionnement)
- [Nommage de branche](#nommage-de-branche)
- [Merge Request](#merge-request)
- [Branches d'intégration](#branches-dintégration)
- [Règles de conception](#règles-de-conception)
- [Créer un composant ](#créer-un-composant-)
- [Guide de développement](#guide-de-développement)
- [Nommage](#nommage)
- [Typescript](#typescript)
- [Processus de dépréciation](#processus-de-dépréciation)
- [Storybook](#storybook)
- [Créer les stories](#créer-les-stories)
- [Ajouter la documentation](#ajouter-la-documentation)
- [Paramétrer la story](#paramétrer-la-story)
- [Mainteneurs : créer une release ](#mainteneurs--créer-une-release-)
## Environnement
### Pré-requis
- node : >=18.16.1
- angular >= 16
### Démarrer
Installer les dépendances
```bash
npm install
```
Lancer Storybook (port 6006)
```bash
npm run storybook:start
```
## Règles de nommage
- Tous les types, interfaces, classes, components, etc. exportés de la librairie doivent être préfixées par `Dsfr`
- Les sélecteurs des composants exportés sont préfixés par `dsfr-`
- Les sélecteurs des composants internes sont préfixés par `edu-`
- Les constantes sont présentées sous forme de type (ex : `DsfrSize`) et de constantes (ex: `DsfrSizeConst`)
- Les deux doivent être exportés dans l'api publique de la lib
## Organisation
- Chaque composant externe est dans un module dédié (ex : `DsfrButtonModule`)
- Chaque composant est placé dans son répertoire dédié nommé à partir du nom de l'élément DSFR (ex: `button`)
- Les matériaux partagés (utilitaires, composants, etc.) sont placés dans le répertoire `shared`
## Code style
- Formatage : Tout fichier de code (html, js/ts, css/scss, json, etc.) doit être formaté automatiquement lors de la
sauvegarde avec Prettier
## Format messages de commit
- Utiliser les conventions [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/)
- Scoper autant que possible avec le nom de l'élément DSFR concerné. Ex. `feat(button): add new capacity`
| Préfixe | Usage |
| ------- | ----------------------------------------------------------------------------------------------- |
| fix | Introduction d'un correctif rétro-compatible |
| feat | Introduction d'une nouvelle fonctionalité rétro-compatible |
| break | Introduction d'un changement non-rétrocompatible |
| doc | Modification de la documentation |
| chore | Tout autre changement non perceptible par l'utilisateur de la lib (code style, build, ci, etc.) |
## Format d'entrée du changelog
Utiliser les même conventions que pour les commits.
> Format : `<type>(scope): description`
- Le préfixe (type) doit être introduit par un tiret (item de liste markdown).
- Si le changement concerne un composant, le scope doit correspondre au suffixe du sélecteur du composant
- La description doit être courte (éviter les déterminants, conjonctions, etc.)
- La longueur d'une entrée de changelog ne doit pas dépasser 120 caractères
## Versionnement
- La bibliothèque utilise le formalisme [semver](https://semver.org/lang/fr/)
- Pour les versions de pré-livraisons un suffixe alpha, beta et rc doit être utilisé avec le format suivant :
- regex: `/\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?/g`
- ex. : `0.1.0-beta.6`, `1.0.0-rc.1`,
## Nommage de branche
- Le nom de la branche doit êre préfixée en utilisant l'un des type autorisés pour les entrées de changelog
- Le préfixe doit être suivi d'un slash puis du nom descriptif de la branche
- Le nom descriptif ne doit pas excéder 3 à 4 mots séparés d'un tiret
- Le nom complet de la branche ne doit utiliser que des caracères minuscules
## Merge Request
- Chaque nouvelle branche concernant un fix ou une feature doit être soumise en MR.
- La MR doit être créée en `Draft` tant qu'elle n'est pas prête pour la relecture.
- Lier le ticket concerné au titre de la MR et ajouter une description si nécessaire.
- La MR doit cibler la branche d'intégration et doit impérativement pouvoir être fusionnée en fast-forward.
- Le cas échéant, utiliser la commande `rebase` pour resynchroniser votre feature-branch avec la branche cible.
### Branches d'intégration
- Hotfixes : master
- Features : develop
## Règles de conception
<em>Chaque composant est encapsulé dans son propre module, sauf certains composants dont le groupe fait partie du même
module (ex : `DsfrAccordionsGroup` est dans le module `DsfrAccordionModule`).</em>
### Créer un composant <a id="creer-composant"></a>
- Créer un module :
```
cd projects/ngx-dsfr-components/src/lib
ng g m dsfr-my-element
```
- Créer le composant :
- Tout ce qui est exposé publiquement sera préfixé par `Dsfr`, exemple : `DsfrButtonComponent`.
```
cd dsfr-button
ng g c dsfr-my-element --skip-tests
```
- Les composants sont tous en encapsulation : ` ViewEncapsulation.None`
De manière à les rendre pleinement réceptifs au style DSFR global ainsi qu'aux éventuelles customisations
- Les sélecteurs des composants sont préfixés par `dsfr-` :
```typescript
// dsfr-button.component.ts
@Component({
selector: 'dsfr-button'
})
```
- Déclarer et exporter le composant dans le module :
```typescript
// dsfr-button.module.ts
@NgModule({
declarations: [DsfrButtonsGroupComponent],
exports: [ DsfrButtonsGroupComponent],
imports: [
CommonModule
]
})
export class DsfrButtonsGroupModule { }
...
```
- Déclarer le module et le(s) composant(s) dans le fichier `index.ts` :
```typescript
// dsfr-button/index.ts
export { DsfrButtonModule } from './dsfr-button.module';
export { DsfrButtonComponent } from './dsfr-button.component';
```
- Publier les composants de la librairie :
```typescript
// public-api.ts
export * from './lib/dsfr-button/index';
...
```
### Guide de développement
#### Nommage
- Noms des méthodes/attributs en anglais et commentaires en français
- Les évènements `@Output` ne doivent pas être préfixés. Privilégier les verbes basiques `select` / `change`, préfixer
s'il y a besoin par un contexte (`linkSelect`, `tabChange`)
- Les attributs `Input()` doivent porter un nom contextuel, ex. ne pas les appeler `model`
- Les méthodes des callback internes doivent être préfixées par 'on' `(click)="onSomethingClick$event)"`
- Respecter le strictCamelCase (pas de capitales consécutives même lorsque le nom intègre un acronyme)
#### Typescript
- Ne pas spécifier le modifieur `public` sur les propriétés publiques
- Privilégier les `Union types` en complément des `enum`, cela permet de positionner l'attribut via sa valeur
directement dans le template sans être obligé d'exposer l'enum au template
```typescript
/**
* Les constantes des niveaux de sévérité DSFR.
*/
export namespace DsfrSeverityConst {
export const INFO = 'info';
export const SUCCESS = 'success';
export const WARNING = 'warning';
export const ERROR = 'error';
}
/**
* Les niveaux de sévérité DSFR exposées sous forme de type.
*/
export type DsfrSeverity = (typeof DsfrSeverityConst)[keyof typeof DsfrSeverityConst];
```
### Processus de dépréciation
Depuis la publication de la version 1.0.0 chaque modification non rétro-compatible doit obligatoirement fair el'objet
d'un processus d edépréciation.
Lorsqu'un élément faisant partie de l'interface publique d'un composant (input, output, slot, modèle) est modifié,
l'ancien élément doit être déprécié. Le cas le plus courant est le renommage d'un input, voici un exemple :
```typescript
/**
* Nouvelle propriété.
*/
@Input() bar:string;
/**
* Ancienne propriété.
*
* @internal
*
* @deprecated since 1.1.0, use 'bar' instead
*/
@Input() set foo(foo:string) {
this.bar = foo;
}
```
L'annotation `@deprecated` vient marquer la propriété dépréciée, elle est suivie d'un commentaire en anglais précisant
la version dans laquelle la propriété a été dépréciée et une indication permettant à l'utilisateur de savoir comment
il doit procéder à présent.
L'ancienne propriété sera supprimée lors de la prochaine version majeure de la bibliothèque.
### Storybook
#### Créer les stories
- Placer un fichier de description du composant et un fichier de stories au même niveau que le composant
```
lib/dsfr-button/button.mdx
lib/dsfr-button/button.stories.ts
```
- Initialiser la story avec un export par défaut :
```typescript
// button.stories.ts
import {DsfrButtonComponent} from '../button.component';
import {Meta} from "@storybook/angular/types-6-0";
// https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
export default {
title: 'DSFR/Button',
component: DsfrButtonComponent,
} ;
export default meta;
...
```
- Créer un type Story pour les différentes stories :
```typescript
type Story = StoryObj<DsfrNoticeComponent>;
```
- Créer une story :
```typescript
export const Default: Story = {
args: {
message: 'mon message'
},
render: (args) => ({
props: args,
template: `
<dsfr-notice [message]="message">Titre</dsfr-notice>`,
}),
```
#### Ajouter la documentation
Créer le fichier .mdx en respectant le template
```md
<Meta of={Stories} title="Quote" />
# Titre
Description
Lien vers la documentation DSFR
- _Module_ : ``
- _Composant_ : ``
- _Tag_ : ``
## Aperçu
<Canvas of={Stories.Default} />
## API
<Controls />
## Exemples
<Canvas of={Stories...} />
```
#### Paramétrer la story
- Afficher le code de la story
> Par défaut, le code sera automatiquement affiché par StoryBook
> cf. [DocsPage](https://storybook.js.org/docs/angular/writing-docs/doc-block-source#working-with-the-docspage).
> _Malheureusement, pour l'instant DocsPage affiche aussi les valeurs par défaut._
- Renommer la story si besoin :
```typescript
Primary.storyName = 'Utilisation via NgModel';
```
- Masquer un contrôle :
- Directement depuis la JSDOC
```typescript
/**
* @internal
*/
myInternalProp: string;
```
- Si besoin, depuis storybook :
```typescript
// button.stories.ts
// Cf. https://storybook.js.org/docs/angular/essentials/controls#disable-controls-for-specific-properties. Dans l'export
argTypes: {
myInternalProp: {
table: {
disable: true;
}
}
}
```
- Masquer un contrôle selon une condition
```typescript
argTypes: {
borderTertiary: { if: { arg: 'variant', eq: 'tertiary' } }
}
```
- Définir des contrôles :
```typescript
// button.stories.ts
// Cf. https://storybook.js.org/docs/angular/essentials/controls. Dans l'export par défaut :
argTypes: {
size: {
control: {type: 'inline-radio'},
options: Object.values(DsfrSize),
defaultValue: DsfrSize.DEFAULT,
}
}
```
- Pour visualiser une action :
```typescript
// button.stories.ts
argTypes: {
handleClick: {
action: true;
}
}
```
- Pour renommer / visualiser une action :
```typescript
argTypes: {
handleClick: {
action: 'onButton';
}
}
```
- Pour que l'action soit prise en compte par le composant
```typescript
parameters: {
actions: false
},
```
## Mainteneurs : créer une release <a id="release-perform"></a>
- Créer le tag en le nommant de manière strictement identique au numéro de version
- Publier une release
- Cf. https://gitlab.forge.education.gouv.fr/dsmen/components/ngx-dsfr-components/-/wikis/Checklist-livraison
- Forcer un déploiement storybook sur Gitlab Pages :
- Se rendre sur https://gitlab.forge.education.gouv.fr/dsmen/components/ngx-dsfr-components/-/pipelines
- Cliquer sur `Run pipeline`
- Sélectionner le tag à déployer
- Renseigner la variable `DEPLOY` (peut importe sa valeur)
- Exécuter le pipeline