npm package: @omng/storybook
Description: Custom design system by Omnilog
Author: Omnilog
Latest version: 4.1.2

<p align="left">
  <img  src="https://img.shields.io/badge/HTML5-E34F26.svg?style=flat-square&logo=HTML5&logoColor=white" alt="HTML5" />
  <img  src="https://img.shields.io/badge/Sass-CC6699.svg?style=flat-square&logo=Sass&logoColor=white" alt="Sass" />
  <img  src="https://img.shields.io/badge/JavaScript-F7DF1E.svg?style=flat-square&logo=JavaScript&logoColor=black" alt="JavaScript" />
  <img  src="https://img.shields.io/badge/React-61DAFB.svg?style=flat-square&logo=React&logoColor=black" alt="React" />
  <img  src="https://img.shields.io/badge/TypeScript-3178C6.svg?style=flat-square&logo=TypeScript&logoColor=white" alt="TypeScript" />
  <img  src="https://img.shields.io/badge/Vite-646CFF.svg?style=flat-square&logo=Vite&logoColor=white" alt="Vite" />
  <img  src="https://img.shields.io/badge/Vitest-6E9F18.svg?style=flat-square&logo=Vitest&logoColor=white" alt="Vitest" />
  <img  src="https://img.shields.io/badge/Jest-C21325.svg?style=flat-square&logo=jest&logoColor=white" alt="Jest" />
  <img  src="https://img.shields.io/badge/Testing_library-E33332.svg?style=flat-square&logo=testinglibrary&logoColor=white" alt="Testing library" />
  <img  src="https://img.shields.io/badge/Storybook-FF4785.svg?style=flat-square&logo=Storybook&logoColor=white" alt="Storybook" />
  <img  src="https://img.shields.io/badge/Semantic_Release-494949.svg?style=flat-square&logo=semanticrelease&logoColor=white" alt="Semantic Release" />
</p>

# Omnilog - Design System

Ce référentiel contient le projet permettant de créer un design system personnalisé pour Omnilog.

Storybook a été rajouté afin de visualiser par le biais d’interfaces les différents composants de manières isolés. Il est possible de modifier les propriétés d’un composant en quelques clics puis de simplement copier le code généré.

Have fun ! 🎉

## 📑 Sommaire

- [🔧 Installation](#-installation)
- [📖 Documentation officielle de Storybook](#-documentation-officielle-de-storybook)
- [📐 Configuration du projet](#-configuration-du-projet)
- [📌 Pipelines GitLab](#-pipelines-gitlab)
- [🤝 Contribution](#-contribution)
- [🚀 Déploiement de Storybook](#-déploiement-de-storybook)
- [📦 Déploiement de la librairie](#-déploiement-de-la-librairie)
- [🦺 Publier une version beta](#-publier-une-version-beta)
- [🌈 Génération des icônes SVG](#-génération-des-icônes-svg)
- [🧪 Éxécution des tests unitaires](#-éxécution-des-tests-unitaires)

## 🔧 Installation

1. Clonez le référentiel à l'aide de [git](https://git-scm.com/downloads) :

```sh
git clone https://git.golinmo.com/bordeaux-tma/storybook
```

2. Accédez au répertoire du projet :

```sh
cd storybook
```

3. Installez les dépendances nécessaires à l'aide de [yarn](https://yarnpkg.com/getting-started/install) :

```sh
yarn install --frozen-lockfile
```

4. Initialisez [Husky](https://typicode.github.io/husky/) :

```sh
yarn run prepare
```

5. Démarrez le serveur de développement :

```sh
yarn start
```

6. Ouvrez votre navigateur préféré et accédez à l'URL suivante : `http://localhost:6006` pour accèder à l'interface de Storybook. C'est ici que vous pourrez visualiser facilement tous les composants du design system.

## 📖 Documentation officielle de Storybook

La documentation officielle de Storybook peut être consultée ici : <https://storybook.js.org/docs>.

Elle fournit des informations détaillées sur l'utilisation de Storybook et de ses fonctionnalités.

## 📐 Configuration du projet

Ce projet est configuré avec les outils et les bonnes pratiques suivants :

- [Commitlint](https://commitlint.js.org/) pour des messages de commit structurés.
- [Husky](https://typicode.github.io/husky/) pour exécuter des scripts lors des événements Git (tels que les commits).
- [ESLint](https://eslint.org/) pour linter et vérifier la syntaxe JavaScript.
- [Stylelint](https://stylelint.io/) pour linter et vérifier les styles CSS.
- [Validate branch name](https://www.npmjs.com/package/validate-branch-name) pour vérifier le nommage de la branche.
- [TypeScript](https://www.typescriptlang.org/) pour ajouter une couche de typage sur JavaScript.
- [Vite](https://vitejs.dev/) pour builder la librairie.
- [Semantic Release](https://semantic-release.gitbook.io/semantic-release) pour gérer notamment les numéros de version et l'historique des modifications.

## 📌 Pipelines GitLab

Ce projet est configuré avec des pipelines GitLab qui exécute les étapes suivantes lorsqu'un push est effectué sur une **Merge Request** ou sur **main** :

1. Vérification du code avec ESLint pour détecter les erreurs et les problèmes de syntaxe (voir [.eslintrc](https://git.golinmo.com/bordeaux-tma/storybook/-/blob/main/.eslintrc)).
2. Vérification des styles avec Stylelint pour s'assurer de la conformité aux règles de styles définies (voir [.stylelintrc](https://git.golinmo.com/bordeaux-tma/storybook/-/blob/main/.stylelintrc)).
3. Vérification du code avec TypeSript pour s'assurer que les typages sont corrects (voir [tsconfig.json](https://git.golinmo.com/bordeaux-tma/storybook/-/blob/main/tsconfig.json)).
5. Éxécution des tests unitaires.
6. Construction et déploiement de Storybook sur un site de développement.
7. Construction et déploiement de la librairie sur npm.

Des pipelines sont également planifiés pour être exécutées à divers moments :

- Effectue un audit de vulnérabilité par rapport aux packages installés (voir [package.json](https://git.golinmo.com/bordeaux-tma/storybook/-/blob/main/package.json)).
- Gestion automatique des mises à jour des dépendances (voir [dependabot.yml](https://git.golinmo.com/bordeaux-tma/storybook/-/blob/main/.gitlab/dependabot.yml)).

## 🤝 Contribution

<details closed>
<summary>Créer une Merge Request</summary>

1. **Mettez-vous à jour**

Rendez-vous sur la branche **main** et récupérez les dernières mises à jour :

```sh
git checkout main
git pull
```

*Note:* si vous rencontrez des problèmes pour mettre à jour la branche **main**, saisissez la commande suivante :

```sh
git checkout main
git reset --hard origin/main
```

2. **Création d'une nouvelle branche**

Travaillez toujours sur une nouvelle branche en lui donnant un nom descriptif. Si vous êtes lié à un problème ou à une fonctionnalité spécifique, incluez le numéro du ticket [Jira](https://omnilog-freq.atlassian.net/jira/software/projects/PI/boards/54?label=Storybook) dans le nom de votre branche :

```txt
<type>/<id?>-<description>
```

Voici à quoi cela pourrait correspondre :

```sh
git checkout -b feat/123-add-button-component
```

Si vous n'avez pas de ticket associé, vous pouvez ignorer la saisie du numéro du ticket au sein de la branche :

```sh
git checkout -b fix/avatar-image
```

3. **Apportez vos modifications**

Développez et testez vos modifications localement.

4. **Commitez vos modifications**

Commitez via un message clair et concis décrivant un maximun la mise à jour apportée.

Format attendu :

```txt
<type>(<scope?>): <id?> <description>

<body?>

<footer?>
```

Voici à quoi cela pourrait correspondre :

```sh
git commit -m 'feat(Button): [PI-123] add Button component'
```

*Note:* si la mise à jour comporte un ou plusieurs changements cassants :

```sh
git commit -m 'build!: [PI-666] upgrade build' -m "BREAKING CHANGE: The files output during the build have been renamed."
```

*Note:* si la mise à jour n'est pas associé à un ticket :

```sh
git commit -m 'fix(Avatar): fix Avatar image'
```

Le message de validation doit suivre le format suivant : <https://www.conventionalcommits.org/en/v1.0.0/>

5. **Envoyer vos modifications vers GitLab**

Envoyez vos modifications vers le référentiel précédemment cloné.

Lors de votre premier **push**, veillez à lier la branche distante avec votre branche locale :

```sh
git push --set-upstream origin <your-branch-name>
```

Pour tous les prochains push, il vous suffira d'effectuer cette commande :

```sh
git push
```

6. **Soumettre une Merge Request**

Créez une MR par rapport au référentiel du projet d'origine. Décrivez clairement les changements et leurs motivations.

Une fois votre MR examinée et approuvée, elle sera fusionnée dans la branche principale **main**.

</details>

<details closed>
<summary>Importer une ressouce</summary>

Lors de l'importation d'une ressource, veillez à bien utiliser son alias.

```js
import { IconLoader } from '@components/Icon/Icons';
```

</details>

<details closed>
<summary>Créer un nouvel alias</summary>

1. Configuration Vite

Tout d'abord, ajouter cet alias dans le fichier `./vite.config.ts` :

```js
export default defineConfig({
  // [...]
  resolve: {
      alias: {
        // [...]
        "@alias": path.resolve(__dirname, "./src/alias"),
      },
    },
});
```

2. Configuration TypeScript

Ajouter également cet alias dans le fichier `./tsconfig.json` :

```json
{
  "compilerOptions": {
    // [...]
    "paths": {
      // [...]
      "@alias/*": ["src/alias/*"],
    },
  }
}
```

3. Configuration Eslint

Et pour finir, ajouter cet alias dans le fichier `./eslintrc` :

```json
{
  "rules": {
    // [...]
    "simple-import-sort/imports": {
      // [...]
      [
        "^@(alias|assets|components|hooks|pages|src|stories|styles|tests|utils)?(/.*|$)"
      ],
    },
  }
}
```

*Note:* une erreur du linter sera remontée si les importations ne sont pas triées dans l'ordre en question.

</details>

<details closed>
<summary>Créer un nouveau composant</summary>

Créer un nouveau dossier respectant la convention **PascalCase** dans `./src/components/` explicitant au mieux votre composant.

Une certaine structure est mise en place au sein du dossier, voici à quoi cela pourrait correspondre :

```sh
├── src/
│   ├── components/
│   │   ├── NewComponent/
│   │   │   ├── styles/
│   │   │   │   └── NewComponent.sass
│   │   │   │   └── ...
│   │   │   ├── tests/
│   │   │   │   └── NewComponent.test.tsx
│   │   │   │   └── ...
│   │   │   ├── constants.ts
│   │   │   ├── NewComponent.tsx
│   │   │   ├── types.ts
│   │   │   └── ...
│   │   └── ...
│   ├── stories/
│   │   ├── NewComponent.stories.tsx
│   │   └── ...
│   └── ...
└── ...
```

<details closed>
<summary>Logique du composant</summary>

Placez la logique JavaScript du composant au sein du fichier `NewComponent.tsx`.

Le composant doit avoir une classe par défaut. Elle commence par le préfix **Omni** suivi du nom du composant (*exemple:* `OmniNewComponent`).

*Note:* la propriété `data-testid` permet à [testing-library](https://testing-library.com/docs/queries/bytestid) de récupérer l'élément lors des tests unitaires, cette valeur doit être la même valeur que le nom de la classe.

```js
// @components/NewComponent/NewComponent.tsx

import clsx from 'clsx';

import '@components/NewComponent/styles/NewComponent.scss';

/**
 * NewComponent component.
 *
 * @param {NewComponentProps} props Props
 * @return {JSX.Element}
 */
function NewComponent({ children, className, ...attr }: NewComponentProps): JSX.Element {
  const classNames = clsx('OmniNewComponent', className);

  return (
    <div
      data-testid="OmniNewComponent"
      {...attr}
      className={classNames}
    >
      <span className="OmniNewComponent__content OmniNewComponent__content--green">
        {children}
      </span>
    </div>
  );
}

export { NewComponent };
```

Pour que le composant soit accesible, il faut l'exporter dans ce fichier `./src/components/index.ts` :

```js
// @components/index.ts

// Components.
// [...]
export * from '@components/NewComponent/NewComponent';

// Types.
// [...]
```

</details>

<details closed>
<summary>Styles du composant</summary>

Placez tous les styles liés à ce composant au sein du fichier `NewComponent.sass`.

*Note:* ce fichier se trouve dans le sous-dossier `/styles`.

```sass
// @components/NewComponent/styles/NewComponent.sass

@use "@styles/functions" as *;
@use "@styles/mixins" as *;
@use "@styles/variables" as *;

.OmniNewComponent {
  background-color: $color-white;
  color: $color-red;
  padding: to-rem(4);

  @include bp-tablet {
    padding: to-rem(6);
  }

  &__content {
    background-color: $color-gray-extra-light;

    &--green {
      background-color: $color-green;
    }
  }
}
```

Le nommage des classes respectent la méthodologie [BEM](https://alticreation.com/bem-pour-le-css/).

Pour que le style soit bien appliqué, il faut bien penser à l'importer dans le fichier du composant (`NewComponent.tsx`).

*Note:* le concept de **Mobile First** est mis en place. Ainsi les composants sont tout d’abord créées pour petites résolutions (*exemple:* smartphones) puis évoluent ensuite progressivement pour s’adapter aux grandes résolutions (*exemple:* ordinateurs). Les **breakpoints** suivants sont disponibles: `bp-tablet` (>= 768px), `bp-laptop` (>= 1024px) et `bp-desktop` (>= 1440px).

</details>

<details closed>
<summary>Histoires sur StoryBook</summary>

Afin de visualiser le composant par le biais de Storybook, il faut créer un fichier `NewComponent.stories.tsx` dans le dossier `./src/stories` afin de créer de multiples histoires :

```js
// @stories/NewComponent.stories.tsx

import type { Meta as MetaStorybook, StoryObj } from '@storybook/react';

import type { NewComponentProps } from '@src/index';
import { NewComponent } from '@src/index';

// Types.
type Meta = MetaStorybook<NewComponentProps>;
type Story = StoryObj<NewComponentProps>;

// Metadata.
const meta: Meta = {
  component: NewComponent,
  tags: ['autodocs'],
  title: 'Composants/Group/NewComponent',
  // [...]
};

export default meta;

// Stories.
export const NewComponentStory: Story = {
  name: "Titre de l'histoire",
  args: {
    children: 'Texte',
    className: 'my-custom-class',
  },
};
```

*Note:* les histoires sont à écrire en **français**.

Plus de détails sur l'écriture d'histoires: ["How to write stories"](https://storybook.js.org/docs/writing-stories).

</details>

<details closed>
<summary>Tests unitaires</summary>

Placez tous les tests unitaires liés à ce composant au sein du fichier `NewComponent.test.tsx`.

*Note:* ce fichier se trouve dans le sous-dossier `/tests`.

```js
// @components/NewComponent/tests/NewComponent.test.tsx

import { composeStories } from '@storybook/react';

import type { NewComponentProps } from '@components/index';
import * as stories from '@stories/NewComponent.stories';
import { render, screen } from '@tests/index';

const { NewComponentStory } = composeStories(stories);

const props: NewComponentProps = {
  id: 'my-custom-id',
};

describe('components/NewComponent', () => {
  it('should renders the expected component', () => {
    render(<NewComponentStory {...props} />);

    const newComponent = screen.getByTestId('OmniNewComponent');

    expect(newComponent).toHaveClass(`OmniNewComponent ${NewComponentStory.args.className}`);
    expect(newComponent).toHaveAttribute('id', props.id);

    // [...]
  });

  // [...]
});
```

</details>

<details closed>
<summary>Constantes globales</summary>

Placez toutes les contantes globales liées à ce composant au sein du fichier `constants.ts`.

Le nom des constantes est en majuscule préfixé par le nom du composant :

```js
// @components/NewComponent/constants.ts

export const NEWCOMPONENT_CONSTANT_1 = ...
export const NEWCOMPONENT_CONSTANT_2 = ...
```

Pour que les constantes soient accessibles, il faut les exporter dans ce fichier `./src/utils/constants.ts` :

```js
// @utils/constants.ts

// [...]
export * from '@components/NewComponent/constants';
```

</details>

<details closed>
<summary>Typage du composant</summary>

Placez tous les types liés à ce composant au sein du fichier `types.ts`.

```js
// @components/NewComponent/types.ts

import type { HTMLAttributes } from 'react';

export type NewComponentProps = HTMLAttributes<HTMLDivElement>;
```

Pour que les types soient accessibles, il faut les exporter dans ce fichier `./src/components/index.ts` :

```js
// @components/index.ts

// Components.
// [...]

// Types.
// [...]
export * from '@components/NewComponent/types';
```

</details>

</details>

## 🚀 Déploiement de Storybook

Le déploiement s'effectue automatiquement sur un registry privé CodeArtifact d'AWS lorsqu'une **Merge Request** est mergée sur la branche principale **main**.

Le projet est publié sur l'environnement de développement suivant : <https://dev-storybook.offprod.app.golinmo.com/>

## 📦 Déploiement de la librairie

Lorsqu'une montée de version est effectuée manuellement via la pipeline GitLab, `semantic-release` s'occupe entre autre de publier la nouvelle version de la librairie sur npm (package `@omng/storybook`).

Le design system est disponible ici : <https://www.npmjs.com/package/@omng/storybook>.

## 🦺 Publier une version beta

Lors de la phase de développement, il est parfois intérressant de déployer des versions beta pour tester correctement la librairie.

À noter qu'une autorisation est nécessaire pour publier une version de la sorte sur le package `@omng/storybook`. Un administrateur de `@omng/storybook` doit vous ajouter à l'équipe afin de bénéficier des droits adéquats.

Une fois réalisé, effectuez la commande suivante pour publier une version beta :

```sh
yarn run publish-beta
```

Il vous sera demander de nommer la version beta. Veillez à ajouter le suffix `-beta.x` où `x` représente un nombre entier positif (plus d'informations sur le nommage des versions : <https://semver.org/>).

Exemple de nommage par ordre de croissance :

```sh
4.0.0-beta.0 < 4.0.0-beta.1 < ... < 4.0.0-beta.12 < 4.0.0
```

## 🌈 Génération des icônes SVG

Les icônes SVG sont générées à partir de fichiers `.svg` présents dans le dossier `./src/assets/icons`. Pour générer les icônes, il suffit d'exécuter la commande suivante :

```sh
yarn run generate-icons
```

Cette commande va générer les composants correspondants sous le nom `Icon{name}.tsx` dans le dossier `./src/components/Icon/src`. Par la suite, ce composant est exporté via le fichier `Icons.tsx`.

Cette génération des icones est possible grâce à différents packages `gulp` installés. Pour plus d'informations veuillez consulter le fichier de configuration [gulpfile.js](https://git.golinmo.com/bordeaux-tma/storybook/-/blob/main/src/gulpfile.js).

*Note:* si vous désirez ajouter un style particulier à une icone, ajoutez un fichier `Icon{name}.scss` dans le dossier `./src/components/Icon/styles` et n'oubliez pas d'ajouter ce fichier à la variables `sassFiles` dans le fichier de configuration `gulpfile.js`.

## 🧪 Éxécution des tests unitaires

Pour lancer les tests :

```sh
yarn run test
```

Pour lancer les tests, tout en surveillant les modifications afin de rééxécuter les tests modifiés :

```sh
yarn run test:watch
```

Pour lancer les tests, tout en collectant les pourcentages de couverture :

```sh
yarn run test:coverage
```