diff --git a/README.md b/README.md index 989d50c..fdc80c7 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ and change the url to whatever you want to use. - [VSCode](https://code.visualstudio.com/) - [React DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) +- [Typescript React code snippets](https://marketplace.visualstudio.com/items?itemName=infeng.vscode-react-typescript) - [vscode-tailwindcss](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) - [vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) diff --git a/docs/DevDocs/Home.md b/docs/DevDocs/Home.md new file mode 100644 index 0000000..b29d664 --- /dev/null +++ b/docs/DevDocs/Home.md @@ -0,0 +1,7 @@ + +Bienvenue à la documentation développeur de SVGLayoutDesigner. + +Cette documentation a pour objectif de familiariser les nouveaux développeur aux outils du projet +et à apprendre à développer des composants sous React. + +Si vous êtes prêt allez sur la page [Pour commencer](Pages/PourCommencer.md) \ No newline at end of file diff --git a/docs/DevDocs/Pages/BasesReact.md b/docs/DevDocs/Pages/BasesReact.md new file mode 100644 index 0000000..6131465 --- /dev/null +++ b/docs/DevDocs/Pages/BasesReact.md @@ -0,0 +1,251 @@ +# Les bases de React + +Pour commencer, je vous recommande fortement de regarder cette vidéo qui expliquera mieux que ce document sur comment fonctionne React : https://www.youtube.com/watch?v=Tn6-PIqc4UM + +Ce document fera quand même de son mieux pour expliquer les bases de React et permettra d'expliquer comment intégrer un composant dans le projet. + +## Qu'est-ce qu'un composant React ? + +Un composant React peut être décrite comme une classe (`class component`) ou une fonction (`functional component`) retournant une description de composant en JSX, un langage permettant de combiner du HTML et du JavaScript. Ces composants ont par convention l'extension de fichier `.jsx` (ou `.tsx` avec TypeScript) + +Un composant est principalement constituer de `props` (propriétés) et d'un `state` (état). Cela est universel pour tous les composants. + +Voici un basique exemple de composant : + +```jsx +function Welcome(props) { + return

Hello, {props.name}

; +} +``` + +> La règle d'or est que le rendu change lorsqu'un props ou un state change. + +Selon comment vous écrivez le composant, classe ou fonctionnelle, la manière de changer les props ou state est différente: + +Pour les props : + +```tsx +// functional +function Welcome(props) { + return

Hello, {props.name}

; +} + +// class +class Welcome extends React.Component { + render() { + return

Hello, {this.props.name}

; + } +} +``` + +Pour l'état: + +```jsx +// Functionnal +const useTicking = (setDate) => { + React.useEffect(() => { + const timerID = setInterval(() => { + setDate(new Date()); + }, 1000); + + return () => { + clearInterval(timerID) + } + }, []); +}; + +export const Clock = (props) => { + const [date, setDate] = React.useState(new Date()); + + useTicking(setDate); + + return ( +
+ { date.toLocaleTimeString() } +
+ ); +}; + + +// class +class Clock extends React.Component { + constructor(props) { + super(props); + this.state = {date: new Date()}; + } + + componentDidMount() { + this.timerID = setInterval( + () => this.tick(), + 1000 + ); + } + + componentWillUnmount() { + clearInterval(this.timerID); + } + + tick() { + this.setState({ + date: new Date() + }); + } + + render() { + return ( +
+ {this.state.date.toLocaleTimeString()} +
+ ); + } +} +``` + +Dans ce projet, il est de convention d'écrire en fonctionnel, car cela a pour avantage de fractionner les composants possèdant beaucoup de fonctions (voir par exemple `ContainerOperations.ts`) et de séparer les les propriétés d'états par des `state hooks` (crochet d'états). + +La documentation qui suivra utilisera donc l'écriture fonctionnelle. Pour en savoir plus sur l'écriture de classe, vous pouvez essayer [le tutoriel de React](https://reactjs.org/tutorial/tutorial.html). + + +## Props + +Les props sont des données entrer d'un composant. Comme pour un composant html ils décrivent simplement ce que le composant doit représenter. + +Exemple : + +```tsx +function Welcome(props) { + return

Hello, {props.name}

; +} +``` + +## State vs Stateless + +Un composant possédant un état interne utilise `React.setState(newState)` dans un composant de classe ou `React.useState(defaultValue)` dans un composant fonctionnel. +Un composant ne possédant pas d'état est dit `stateless`. + +Voici donc un exemple de composant React **avec état** : + +```tsx +// Clock.tsx +interface IClockProps { +} + +const useTicking = (setTime: React.Dispatch>) => { + React.useEffect(() => { + const timerID = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => { + clearInterval(timerID) + } + }, []); +}; + +export const Clock: React.FC = (props) => { + const [time, setTime] = React.useState(new Date()); + + useTicking(setTime); + + return ( +
+ { time.toLocaleTimeString() } +
+ ); +}; + +``` + +Et voici la version **sans état** : + +```tsx +// Clock.tsx +interface IClockProps { + time: Date +} + +export const Clock: React.FC = ({ time }) => { + return ( +
+ { time.toLocaleTimeString() } +
+ ); +}; + +// Parent.tsx +const useTicking = (setTime: React.Dispatch>) => { + React.useEffect(() => { + const timerID = setInterval(() => { + setTime(new Date()); + }, 1000); + return () => { + clearInterval(timerID); + }; + }, []); +}; + +export const Parent: React.FC = ({ time }) => { + const [time, setTime] = React.useState(new Date()); + + useTicking(setTime); + + return ( + + ); +}; +``` + +Comme vous pouvez le voir, la différence est que la gestion d'état est donnée au composant parent. Cela permet de réduire les mises à jours de rendu en parallèles et de faire le rendu seulement si l'état du composant parent change. + +Vous avez probablement remarqué l'usage de `React.useState()` et `React.useEffect()`. Lorsque l'on utilise une fonction avec le mot clé `use` de React, on utilise ce qu'on appelle un `hook` (crochet). + +Il existe plusieurs type de hook et chacun ont leurs propre effet. Dans ce projet, nous utilisont principalement `useState` et `useEffect` mais il y a aucune interdiction pour utiliser d'autres types. + +### `useState` + +`useState` permet de lier à l'état interne du composant une variable. + +La fonction nous fourni 2 paramètres dans une liste. La valeur courante et le setter de cette valeur : + +```jsx +const [value, setValue] = React.useState(defaultValue); +``` + +### `useEffect` + +`useEffect` permet de lancer une fonction d'effet lors d'un rendu. Il prend en paramètres : +- une fonction callback d'effet lors du rendu qui retourne lui aussi retourne une fonction d'effet lors de la suppression du rendu. +- Il prend aussi optionnellement un liste de dépendance permettant de déterminer les conditions de rendu + +Revenons sur l'exemple de `useTicking`: + +```tsx +const useTicking = (setTime) => { + React.useEffect(() => { + // componentDidMount + const timerID = setInterval(() => { + setTime(new Date()); + }, 1000); + + return () => { + // componentWillUnmount + clearInterval(timerID); + }; + }, []); +}; +``` + +Décrivons ce quelle fait : +- La callback de `useEffect` appelle `setInterval` afin d'exécuter une fonction `setTime` toute les seconde. +- Cette callback retourne la fonction de suppression de `setInterval` qui sera exécutée si le composant est disposé. +- `useEffect` possède un tableu de dépendance mais elle est vide. Cela veut dire que la callback sera exécuté qu'une seule est unique fois sur toute la durée de vie du composant + +Si on avait enlevé le tableau de dépendance, on aurait eu une boucle infinie ! Car rien n'aurait limité l'exécution de la callback à chaque rendu créé par `setTime`. + +> Une règle d'or est qu'il faut mettre un tableau de dépendance lorsque l'on utilise un setteur d'état car il y aura a coup sûr une boucle infinie s'il y en a pas + +> Contraposée: Il n'est pas nécessaire de mettre un tableau de dépendances s'il n'y pas de setteur d'état dans la callback + +> Il est de convention de mettre les fonctions `useEffect` dans une fonction nommée afin de déterminer ce qu'elle fait. Il est possible d'appeler plusieurs fois `useEffect`, donc il n'est pas dangereux de dissocier les effets. + + +Si vous avez compris, essayons de créer notre [premier composant React](PremierComposantReact.md). \ No newline at end of file diff --git a/docs/DevDocs/Pages/Heroicon.md b/docs/DevDocs/Pages/Heroicon.md new file mode 100644 index 0000000..ac82f9b --- /dev/null +++ b/docs/DevDocs/Pages/Heroicon.md @@ -0,0 +1,41 @@ +# Heroicon + +Cette page est totalement optionnel pour apprendre à faire un composant. Mais elle vous permettra d'apprendre à utiliser [Heroicon](https://heroicons.com/). + +Heroicon est un ensemble de SVG utilisable sous forme JSX avec React et Tailwind CSS. + + +# Continuation de l'exemple de Premier Composant React + +L'icône Home peut être importé comme tout composant React. + +Il existe plusieurs type de chaque icône de Heroicon : `outline`, `solid`, `mini`. Utilisons donc `outline`. + +```tsx +import { HomeIcon } from '@heroicons/react/outline'; + +... + +export function Home({ goHome }: IHomeProps): JSX.Element { + const defaultIndex = Math.floor(Math.random() * (colors.length - 1)); + const [index, setIndex] = useState(defaultIndex); + + const selectedColor = colors[index]; + const className = `${selectedColor}`; + return ( + + ); +} + +``` \ No newline at end of file diff --git a/docs/DevDocs/Pages/PourCommencer.md b/docs/DevDocs/Pages/PourCommencer.md new file mode 100644 index 0000000..56eb977 --- /dev/null +++ b/docs/DevDocs/Pages/PourCommencer.md @@ -0,0 +1,43 @@ +# Pour commencer + +Je vous recommande de lire [README.md](../../../README.md) si ce n'est pas encore fait +et de mettre en place le projet selon ce qui est écrit. + +La documentation fera référence à la configuration suivante : +- [VSCode](https://code.visualstudio.com/) pour l'IDE +- [pNPm](https://pnpm.io/fr/) pour le gestionnaire de paquet +- [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) pour l'outil d'analyse de projet React +- [Typescript React code snippets](https://marketplace.visualstudio.com/items?itemName=infeng.vscode-react-typescript) pour les snippets React + +Si vous n'avez pas installé l'un de ces outils, je vous le recommande **fortement**. + +# Structure du projet + +Commençons par nous familiariser avec la structure du projet: 90% du temps, les modifications que vous allez faire seront dans `src/`. +Si vous souhaitez vous familiariser avec le dossier du projet, lisez [Project_Structure](../../Project_Structure.md). + +Le dossier est composé de la manière suivante: + +``` +./src +├── assets +├── Components +├── dts +├── Enums +├── Events +├── Interfaces +├── test +├── tests +├── utils +├── index.scss +├── main.tsx +└── vite-env.d.ts +``` + +Le point d'entrer est le `main.tsx`, mais vous aurez rarement le besoin de le modifier sauf pour ajouter des fonctionnalité sur le scope global. + +Les dossiers principaux que vous aurez besoin d'utiliser pour développer un nouveau composant seront : `Components`, `Enums`, `Interfaces`, `utils` (et `assets` si vous mettez des images ou polices d'écriture). + +Les autres dossiers ne sont pas nécessaire pour faire un composant, mais sont utilisés pour développer d'autres systèmes comme les évents, les définitions typescript et les tests. + +Si vous avez compris, allons à la suite et regardons [les bases de React](BasesReact.md). \ No newline at end of file diff --git a/docs/DevDocs/Pages/PremierComposantReact.md b/docs/DevDocs/Pages/PremierComposantReact.md new file mode 100644 index 0000000..6d8ca2a --- /dev/null +++ b/docs/DevDocs/Pages/PremierComposantReact.md @@ -0,0 +1,297 @@ +# Notre premier composant React + +Si vous êtes arrivé ici, c'est que vous avez compris ce qu'est un composant React et qu'il est composant de `props` et d'un `state`. + +Mettons donc en pratique ce que vous avez vu. + +Voici le cahier de charge fonctionnel du composant à implémenter : + +Fonction: +- On veut ajouter un bouton `Home` dans l'éditeur pour revenir au menu principal + +Contraintes principales : +- Le bouton `home` ne doit pas rafraîchir la page + +Contraites secondaires : +- Le bouton est positionné en bas sur la barre +- Le bouton change de couleur à chaque fois que le curseur entre dedans +- Le bouton possède un icône home + +## Initialiser un composant + +Commençont par créer le composant sans le lier forcément au reste du projet. + +Par convention comme il est dit dans la section [Pour commencer](PourCommencer.md), créons le fichier `Home.tsx` dans un dossier `Home/` dans `src/Components`. Il est recommandé de toujours créer un dossier pour le composant pour plus tard si on veut ajouter des styles propres au composant ou si on veut faire des tests. + +Maintenant créons le squelette du composant. Pour rappel, on utilise la syntaxe *fonctionnelle* (i.e. une fonction = un composant). + +```tsx +// Home.tsx +export function Home(): JSX.Element { + return <>; +} +``` + +Note: vous pouvez utiliser le snippet `tsrfc` si vous utilisez l'extension de VSCode. + +La convention de nommage indique qu'il faut prioriser les fonctions nommées (donc pas de flèches fonctions). + +Mettons le bouton html et mettons du texte dedans : + +```tsx +export function Home(): JSX.Element { + return ( + + ); +} +``` + + +## Appeler une fonction + +Pour que le bouton appelle une fonction nous allons utiliser la propriété `onClick`. Pour lui équiper d'une variable/fonction, on la met entre accolades `prop={var}`. + +```tsx +function GoHome(): void { + location.reload(); +} + +export function Home(): JSX.Element { + return ( + + ); +} +``` + +Cela devrait être suffisant pour revenir au menu principal. Appelons-le dans l'editeur. + + +## Ajouter le composant dans l'editeur + +Tout ce qui est UI doit se mettre dans `UI.tsx`. +On sait que l'on doit l'ajouter dans la **barre** à gauche, soit `Bar.tsx` + +Commençons par importer le composant dans `Bar.tsx`: + +```tsx +// Bar.tsx +import { Home } from '../Home/Home'; +... +``` + +Puis ajoutons-le dans la vue : + +```tsx +// Bar.tsx +export function Bar(props: IBarProps): JSX.Element { + return ( +
+ ... + +
+ ); +} +``` + +Testez avec `pnpm dev`. Et ouvrez la page sur `http://localhost:5173`. + +Et voilà ! Vous avez fini ! + +... + +Sauf que vous n'avez respecté aucune des contraintes. + +Faisons mieux. Utilisons les `props` et le `state` de l'application. + + +## Props et state + +Le composant doit restaurer l'état du menu principal. + +Dans le menu principal, lorsque l'on clique sur `Start from scratch`, le composant doit forcément changer un état pour cacher le menu. + +Ce que l'on veut faire est donc de renverser cette opération, autrement dit changer l'état qui permet de cacher le menu pour le remontrer. + +Allons donc dans le composant `MainMenu.tsx` et analysons le contenu du bouton `Start from scratch`. + +```tsx +// MainMenu.tsx + ... + default: + return ( +
+ + +
+ ); +``` + +On trouve la fonction `onClick` suivante : + +```tsx +// MainMenu.tsx +{ + setWindowState(WindowState.Loading); + props.newEditor(); +} +``` + +Aha! On trouve une fonction qui change d'état. Cependant ! On découvre aussi qu'elle appelle une fonction `newEditor`. + +Instinctivement, vu que l'on cherche une fonction qui permet de changer d'état, on pourrait croire que c'est `setWindowState` qui gère mais on va vite comprendre que ce n'est pas elle. + +Pour rappel `setWindowState` est un setteur d'état, et qu'elle est définie **dans** le composant. Autrement dit, elle ne gère pas l'état de l'application. De plus, un indice indique que le l'état du menu passe de `Main` à `Loading`. Elle ne fait donc que changer sa propre vue pour passer à celle d'une animation de loading que l'on peut voir plus haut dans le `switch`. + +`newEditor` étant une fonction donnée par `props`, doit donc correspondre à la fonctionalité que l'on veut renveser. + +Regardons donc le composant parent de `MainMenu`: `App`. + +```tsx +// App.tsx +... + NewEditor( + setEditorState, setLoaded + )} + loadEditor={(files: FileList | null) => LoadEditor( + files, + setEditorState, + setLoaded + )} + /> +... +``` + +Cette fonction `newEditor` appelle donc une autre fonction `NewEditor`. + +```tsx +export function NewEditor( + setEditorState: Dispatch>, + setLoaded: Dispatch> +): void { + // Fetch the configuration from the API + FetchConfiguration() + .then((configuration: IConfiguration) => { + // Set the editor from the given properties of the API + const editorState: IEditorState = GetDefaultEditorState(configuration); + + setEditorState(editorState); + setLoaded(true); + }, (error) => { + console.debug('[NewEditor] Could not fetch resource from API. Using default.', error); + setLoaded(true); + }); +} +``` + +Cette fonction à l'air assez compliquée, mais on sait qu'elle utilise `setEditorState` et `setLoaded`. Et c'est tout ce qui nous intéresse. + +Donc pour renverser l'état de `App` il faut appeler soit `setEditorState` ou soit `setLoaded`. En analysant un peu plus près `App.tsx`, on remarque l'évidante condition permettant d'afficher ou non le menu principal : + +```tsx +// App.tsx + ... + if (isLoaded) { + return ( +
+ +
+ ); + } + + return ( +
+ +
+``` + +Ainsi, on sait qu'il faut juste appeler `setLoaded` le setteur d'état de `isLoaded`: + +```tsx +// App.tsx +const [isLoaded, setLoaded] = useState(false); +``` + +Cependant comment appeler cette fonction depuis `Home` ? Et bien nous allons utiliser les `props` pour passer la fonction en dessous. + +Comme pour `MainMenu`, dans `Home.tsx` nous allons mettre la fonction `GoHome` dans les props : + +```tsx +import React from 'react'; + +interface IHomeProps { + goHome: () => void +} + +export function Home({ goHome }: IHomeProps): JSX.Element { + return ( + + ); +} +``` + +Note: vous pouvez utiliser `Home(props: IHomeProps)` et appeler `props.goHome` comme tout le reste du projet, mais le destructuring d'objet est autorisé. + +Home est utilisé dans les composants Bar, UI, et Editor avant d'être appelé dans App. Il faut faire la même chose, ajouter la fonction dans les props et l'appeler dans le composant : + +```tsx +// Bar.tsx +interface IBarProps { + goHome: () => void +} +... +export function Bar(props: IBarProps): JSX.Element { + return ( +
+ ... + +
+ ); +} +``` +*Faites la même chose pour UI et Editor.* + +Enfin dans `App.tsx`, créez la fonction permettant de changer `isLoaded` vers `false`: + +```tsx + ... + if (isLoaded) { + return ( +
+ { + setLoaded(false); + }} + /> +
+ ); + } + ... +``` + +Félicitation vous avez correctement développé votre premier composant Home ! + +Essayons maintenant de faire le reste dans le prochain chapitre avec [`TailwindCSS`](Tailwind.md) \ No newline at end of file diff --git a/docs/DevDocs/Pages/SmartComponent.md b/docs/DevDocs/Pages/SmartComponent.md new file mode 100644 index 0000000..e632da8 --- /dev/null +++ b/docs/DevDocs/Pages/SmartComponent.md @@ -0,0 +1,146 @@ +- [Préface](#préface) +- [Customiser et build le projet (recommandé)](#customiser-et-build-le-projet-(recommandé)) + * [Configurer les options de build](#configurer-les-options-de-build) + * [Build le projet](#build-le-projet) +- [Télécharger les défauts](#télécharger-les-défauts) + * [Télécharger les ressources](#télécharger-les-ressources) +- [Utiliser dans modeler](#utiliser-dans-modeler) + +# Préface + +Ce document a pour but d'expliquer comment intégrer SVGLayoutDesigner dans un projet. + +Le projet se compile avec `npm run build` grace à Vite. +Avec les fichiers générés dans `dist` nous allons créer un nouveau composant modeler par injection. +(Plus tard les fichiers "Release" seront fournies par azure-pipeline par artefacts). + +Il utilise le framework JS React pour faire le rendu. + +Les projets modeler vont communiquer avec le composant par [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent) et par REST via un webservice dans les options de build. + +Il y deux manières de récupérer les builds du projets: +- Rebuild le projet (recommandé) +- Récupérer des prébuild + +# Customiser et build le projet (recommandé) + +Customiser le build du projet permet de modifier les urls de l'API et de personnaliser des fonctionnalités. + +## Configurer les options de build + +Il y a deux fichiers principaux à configurer : +- `.env.production.local`: pour configurer les URLs d'API +- `src/utils/default.ts`: pour configurer les fonctionnalités + +Copiez `.env.production` vers `.env.production.local` et modifiez-le comme bon vous semble : + +``` +VITE_API_FETCH_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetSVGLayoutConfiguration +VITE_API_SET_CONTAINER_LIST_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/SetContainerList +VITE_API_GET_FEEDBACK_URL=https://localhost/SmartMenuiserieTemplate/Service.svc/GetFeedback +``` + +Vous pouvez modifiez `src/utils/default.ts` mais ne le committez pas. + +## Build le projet + +Lancer la commande `npm run build` dans le projet. + +Les fichiers seront fournis dans `dist`. + + +# Télécharger les défauts + +Les defauts utilise l'API de SmartMenuiserieTemplate sur `localhost` + +## Télécharger les ressources + +Sur Azure DevOps, dans ce projet *SmartConfigurator*, allez dans **Pipelines**. Cliquez sur la pipeline [SVGLayoutDesignerReact](https://techformsa.visualstudio.com/SmartConfigurator/_build?definitionId=9) + +Cliquez sur le dernier run de la branche `dev` ou (`master` pour une version stable). + +Cliquez sur Job (s'il a un statut **Success**) + +Cliquez sur `1 artifact`: +![image.png](/.attachments/image-28004315-69f2-4452-ab83-7f3823f42699.png) + +Cliquez sur le menu hamburger et cliquez sur **Download Artifacts** : +![image.png](/.attachments/image-76f5786f-ef6a-4fd9-8bb7-b7b294c140b6.png) + + +# Utiliser dans modeler + +- Créez un dossier `svg-layout-designer` dans le dossier `Component`. + +- Puis copier les fichiers de build dans le dossier. (copiez l'entièreté du dossier `dist` ou extrayez le zip selon la méthode). + +- Modifiez le fichier `index.html` du composant pour changer les chemins absolus en relatifs: + +```diff + + + + + + + + Vite + React + TS +- ++ +- ++ + + +
+ + + +``` + +- Si **besoin**, modifier le fichier `smartcomponent/svg-layout-designer.html` pour avoir la bonne url relative à la basename de l'url : + +```diff +- +``` + +- Ensuite dans Visual Studio, incluez les fichiers et compilez. + +- Enfin sur Modeler, un nouveau composant devrait être disponible. + +- Pour l'utiliser dans un viewModel, créez un observable de type `SVGLayoutDesigner` et assignez-le dans les propriétés du composant : + +```typescript +ConfiguratorViewModel.ts +... + +private OSVGLayoutDesigner: KnockoutObservable = ko.observable(null); + +... +``` + +![image.png](/.attachments/image-63b863d7-9793-459f-b940-a1a26d905053.png) + +- Exemple d'utilisation: + +```typescript +public AddContainer() { + // Récupère la configuration de l'éditeur + this.OSVGLayoutDesigner().GetEditorState((state: SVGLD.IEditorState) => { + // Création d'un nouveau conteneur du premier type de container de la config dans le conteneur sélectionné + const config = state.configuration; + const containerConfig = config.AvailableContainers.shift(); + const type = containerConfig.Type; + this.OSVGLayoutDesigner().AppendContainerToSelectedContainer(type); + }); +} + +public DeleteContainer() { + // Supprime le conteneur sélectionné + this.OSVGLayoutDesigner().GetCurrentHistoryState((state: SVGLD.IHistoryState) => { + const selectedContainer = state.selectedContainerId; + this.OSVGLayoutDesigner().DeleteContainer(selectedContainer); + }) +} +``` \ No newline at end of file diff --git a/docs/DevDocs/Pages/Tailwind.md b/docs/DevDocs/Pages/Tailwind.md new file mode 100644 index 0000000..eeeb5e9 --- /dev/null +++ b/docs/DevDocs/Pages/Tailwind.md @@ -0,0 +1,240 @@ +# Tailwind CSS + +Tailwind CSS est un framework CSS proposant des classes utilitaires comme `flex, pt-4, text-center`. +Ce framework permet d'optimiser notament la génération de css et d'éviter la duplication de code lorsque `Vite` compile le projet. + +On peut confirmer cela en regardant le fichier `.css` généré après avoir lancé la commande `pnpm build`. + +Il utilise `index.scss` si on veut regrouper les propriétés de tailwind dans une classe. + +Il utilise `tailwind.config.cjs` pour configurer et étendre quelques propriétés comme la couleur. + + +# Continuation de l'exemple de Premier Composant React + +Rappelons les contraintes secondaires du bouton Home : +- Le bouton est positionné en bas sur la barre +- Le bouton change de couleur à chaque fois que le curseur entre dedans +- Le bouton possède un icône home + +Essayons Tailwind avec React. + +Je vous recommande fortement d'avoir [la documentation de Tailwind](https://tailwindcss.com/) sous les yeux pour suivre ce que font les propriétés. + +## Positionner avec Tailwind + +Pour positionner le bouton tout en bas il faut modifier le layout de la barre + +Voici le layout actuel du composant Bar. + +```tsx +// Bar.tsx +
+ (...) + (...) + (...) + (...) + +
+``` + +```scss +// index.scss + .bar { + @apply fixed z-20 flex flex-col top-0 left-0 + h-full w-16 bg-slate-100 + } +``` + +Traduisont donc ceci en css: + +```css +.bar { + display: flex; /* flex */ + flex-direction: column; /* flex-col */ + position: fixed; /* fixed */ + top: 0; /* top-0 */ + left: 0; /* left-0 */ + z-index: 20; /* z-20 */ + width: 4rem; /* w-16 */ + height: 100%; /* h-full */ +} +``` + +On va juste nous interesser sur `flex` et `flex-col` du coup car elles decident du layout des éléments enfants. + +Pour espacer deux éléments dans un layout flex, il y a plusieurs manière de le faire : +- On peut rajout un `div` d'espacement avec la propriété `grow` +- On peut wrapper tous les `BarIcon` et utiliser `place-content-between` dans `.bar` + +On va utiliser la premiere méthode, mais libre à vous la méthode. + +Pour ajouter une classe sur React, on utilise le mot-clé `className`. C'est pour ne pas rentrer en conflit avec le mot-clé `class` servant de classe d'objet en JavaScript que ce mot-clé a été choisi. + +```tsx +// Bar.tsx +
+ (...) + (...) + (...) + (...) +
+ +
+``` + +Et voilà ! Aussi simple que cela. + +Continuons avec les couleurs avec le curseur. Cette fois ci, on va utiliser React. + + +# Utiliser Tailwind avec React + +Revenons sur le composant Home définie par un simple bouton : + +```tsx +// Home.tsx +import React from 'react'; + +interface IHomeProps { + goHome: () => void +} + +export function Home({ goHome }: IHomeProps): JSX.Element { + return ( + + ); +} +``` + +Dans ce composant, initialisons la liste des couleurs que l'on veut utiliser. Les couleurs de Tailwind sont définies sur cette page : `https://tailwindcss.com/docs/customizing-colors`. + +Il nous suffit donc de juste mettre une liste de mots-clés de couleurs : + +```tsx +// Home.tsx +... + +const colors = [ + 'bg-state-500', + 'bg-gray-500', + 'bg-zinc-500', + 'bg-neutral-500', + 'bg-stone-500', + 'bg-red-500', + 'bg-orange-500', + 'bg-amber-500', + 'bg-yellow-500', + 'bg-lime-500', + 'bg-emerald-500', + 'bg-teal-500', + 'bg-cyan-500', + 'bg-sky-500', + 'bg-blue-500', + 'bg-indigo-500', + 'bg-violet-500', + 'bg-purple-500', + 'bg-fuchsia-500', + 'bg-pink-500', + 'bg-rose-500' +]; +``` + +Et d'en sélectionner une au hazard. + +```tsx +// Home.tsx +const colors = [ + ... +]; + +export function Home({ goHome }: IHomeProps): JSX.Element { + const index = Math.floor(Math.random() * (colors.length - 1)); + const selectedColor = colors[index]; + const className = `${selectedColor}`; + return ( + + ); +} +``` + +Vous vous demandez peut-être pourquoi je n'ai pas juste interpolé une liste de couleurs (`red`, `blue` etc.) au lieux de la *liste de propriétés de couleurs d'arrière-plan* (`bg-red-500`, `bg-blue-500`...). +Cela est expliqué par l'algorithme d'optimisation de fichier CSS de Tailwind. + +Tailwind CSS purge les classes non utilisées dans son framework en scannant tous les fichiers, cela permet d'avoir un fichier css final léger mais en contrepartie, les opérations d'interpolation ne fonctionne pas. +Vous pouvez vérifier la liste de type fichier scanné dans `tailwind.config.cjs`. + +Note : Cela veut dire que l'on aurait pas créer un fichier de preload appelé `colors.tw` et ajouter ce format `.tw` dans le fichier de config pour qu'il le scanne. Ainsi pouvoir enfin interpoler dans le fichier `.tsx` mais cela revient au même finalement que de faire la liste. + +On a donc enfin un arrière-plan qui change mais n'avons nous pas oublié quelque chose ? +Oui ! Il faut changer la couleur quand la souris passe par dessus ! + +Cette fois-ci, pouvez-vous le faire sans lire la solution ? + +Indice: Cela implique l'utilisation de `state` ;) + +# Réponse + +Suivons donc l'indice et créons un hook d'état afin de sélectionner la couleur et de pouvoir la changer quand la souris passe dans le composant + +```tsx +// Home.tsx +export function Home({ goHome }: IHomeProps): JSX.Element { + const defaultIndex = Math.floor(Math.random() * (colors.length - 1)); + const [index, setIndex] = useState(defaultIndex); + + const selectedColor = colors[index]; + const className = `${selectedColor}`; + return ( + + ); +} +``` + +Ensuite utilisons la propriété `onMouseEnter` pour jeter l'évent. + +```tsx +// Home.tsx +export function Home({ goHome }: IHomeProps): JSX.Element { + const defaultIndex = Math.floor(Math.random() * (colors.length - 1)); + const [index, setIndex] = useState(defaultIndex); + + const selectedColor = colors[index]; + const className = `${selectedColor}`; + return ( + + ); +} +``` + +Et voilà vous avez enfin terminé avec Tailwind CSS ! + +Amusez-vous un peu avec Tailwind CSS pour ajuster le style du bouton (padding, margin, transitions etc.) et découvrir quelques propriétés. + +Finissons ce tutoriel avec [Heroicon](Heroicon.md). diff --git a/src/Components/Bar/Bar.tsx b/src/Components/Bar/Bar.tsx index ecfb20e..0717b43 100644 --- a/src/Components/Bar/Bar.tsx +++ b/src/Components/Bar/Bar.tsx @@ -1,5 +1,6 @@ -import { ClockIcon, CubeIcon, LinkIcon, MailIcon } from '@heroicons/react/outline'; import * as React from 'react'; +import { ClockIcon, CubeIcon, LinkIcon, MailIcon } from '@heroicons/react/outline'; +import { ClockIcon as ClockIconS, CubeIcon as CubeIconS, LinkIcon as LinkIconS, MailIcon as MailIconS } from '@heroicons/react/solid'; import { BarIcon } from './BarIcon'; interface IBarProps { @@ -12,6 +13,7 @@ interface IBarProps { toggleSymbols: () => void toggleTimeline: () => void toggleMessages: () => void + goHome: () => void } export const BAR_WIDTH = 64; // 4rem @@ -23,25 +25,41 @@ export function Bar(props: IBarProps): JSX.Element { isActive={props.isSidebarOpen} title='Components' onClick={() => props.toggleSidebar()}> - + { + props.isSidebarOpen + ? + : + } props.toggleSymbols()}> - + { + props.isSymbolsOpen + ? + : + } props.toggleTimeline()}> - + { + props.isHistoryOpen + ? + : + } props.toggleMessages()}> - + { + props.isMessagesOpen + ? + : + } );