From 8418ca4a74812e9618b920ca651505ba50665f75 Mon Sep 17 00:00:00 2001 From: GauthierWebDev Date: Sun, 20 Apr 2025 20:42:47 +0200 Subject: [PATCH] style: Reorder props in Snippet component --- app/components/Button.tsx | 2 +- app/components/Highlight.tsx | 71 ++++++-- app/components/Prose.tsx | 2 - app/components/Snippet.tsx | 93 ++++++++++ app/components/Tabs.tsx | 131 ++++++++++++++ app/layouts/prism.css | 223 ++++++++++++++++++++---- app/pages/communaute/partages/+Page.mdx | 6 - app/pages/docs/react/+Page.mdx | 150 ++++++++++++++++ app/pages/docs/react/tabs.tsx | 94 ++++++++++ app/partials/HeroSection.tsx | 90 +++------- app/partials/Navigation.tsx | 2 +- 11 files changed, 738 insertions(+), 126 deletions(-) create mode 100644 app/components/Snippet.tsx create mode 100644 app/components/Tabs.tsx create mode 100644 app/pages/docs/react/+Page.mdx create mode 100644 app/pages/docs/react/tabs.tsx diff --git a/app/components/Button.tsx b/app/components/Button.tsx index 177b042..8e5e0ab 100644 --- a/app/components/Button.tsx +++ b/app/components/Button.tsx @@ -42,6 +42,6 @@ export function Button(props: ButtonProps) { href={props.href} /> ) : ( - + + )} + + + + {selectedTab() && ( +
+ + {selectedTab().code} + +
+ )} + + + ); +} diff --git a/app/components/Tabs.tsx b/app/components/Tabs.tsx new file mode 100644 index 0000000..4f072d7 --- /dev/null +++ b/app/components/Tabs.tsx @@ -0,0 +1,131 @@ +import type { JSX, Accessor, Setter } from "solid-js"; + +import { + createContext, + useContext, + createSignal, + onMount, + For, +} from "solid-js"; +import { Button } from "@/components/Button"; +import clsx from "clsx"; + +type TabType = { + label: string; + value: string; +}; + +type TabsContextType = { + selectedTab: Accessor; + setSelectedTab: Setter; + tabs: Accessor; + addTab: (tab: TabType) => void; +}; + +const TabsContext = createContext({ + selectedTab: () => "", + setSelectedTab: () => {}, + tabs: () => [], + addTab: () => {}, +}); + +export default function Tabs(props: { + defaultSelectedTab?: string; + children: JSX.Element; +}) { + const [selectedTab, setSelectedTab] = createSignal( + props.defaultSelectedTab || "", + ); + const [tabs, setTabs] = createSignal([]); + + const addTab = (tab: TabType) => { + console.log("Adding tab", tab); + + setTabs((prevTabs) => { + // Append to the end of the array and make sure it's unique + if (prevTabs.some((t) => t.value === tab.value)) { + return prevTabs; + } + + return [...prevTabs, tab]; + }); + }; + + return ( + +
+
+
    + + {(tab) => ( +
  • + setSelectedTab(tab.value)} + /> +
  • + )} +
    +
+
+
{props.children}
+
+
+ ); +} + +function TabItem(props: { + tab: TabType; + isSelected: boolean; + select: () => void; +}) { + return ( + + ); +} + +Tabs.Item = (props: { + label: string; + value: string; + children: JSX.Element; +}) => { + const tabsContext = useContext(TabsContext); + if (!tabsContext) { + throw new Error("Tabs.Item must be used within Tabs"); + } + + onMount(() => { + console.log("Mounting tab", props.label); + tabsContext.addTab({ label: props.label, value: props.value }); + }); + + return ( +
+ {props.children} +
+ ); +}; diff --git a/app/layouts/prism.css b/app/layouts/prism.css index 8a1018a..69fe39b 100644 --- a/app/layouts/prism.css +++ b/app/layouts/prism.css @@ -1,46 +1,195 @@ -pre[class*=language-] { - color: var(--color-slate-50) +code[class*="language-"], +pre[class*="language-"] { + color: #383a42; + background: none; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; } -.token.tag, -.token.class-name, -.token.selector, -.token.selector .class, -.token.selector.class, -.token.function { - color: var(--color-pink-400) +pre[class*="language-"]::selection, +pre[class*="language-"] ::selection, +code[class*="language-"]::selection, +code[class*="language-"] ::selection, +pre[class*="language-"]::-moz-selection, +pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, +code[class*="language-"] ::-moz-selection { + background: #DDE3EA; + text-shadow: none; } -.token.attr-name, -.token.rule, -.token.pseudo-class, -.token.important { - color: var(--color-slate-300) +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } } -.token.keyword, -.token.module { - color: var(--color-pink-400) -} -.token.attr-value, -.token.class, -.token.string, -.token.property { - color: var(--color-sky-300) -} -.token.punctuation, -.token.attr-equals { - color: var(--color-slate-500) -} -.token.unit, -.language-css .token.function { - color: var(--color-teal-200) +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; } .token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #a0a1a7; +} +.token.plain-text, +.token.punctuation { + color: #383a42; +} +.token.selector, +.token.tag { + color: #e45649; +} +.token.property, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.attr-name, +.token.deleted { + color: #e1aa76; +} +.token.string, +.token.char, +.token.attr-value, +.token.builtin, +.token.inserted { + color: #50a14f; +} .token.operator, -.token.combinator { - color: var(--color-slate-400) +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #46a6b2; } -.prism-code { - margin: 0 +.token.function { + color: #4078f2; } -.prism-code + .prism-code { - margin-bottom: 1rem +.token.atrule, +.token.keyword, +.token.regex, +.token.important, +.token.variable { + color: #a626a4; } +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} +.token.entity { + cursor: help; +} +pre.line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; +} +pre.line-numbers > code { + position: relative; +} +.line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8em; + width: 3em; + letter-spacing: -1px; + border-right: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.line-numbers-rows > span { + pointer-events: none; + display: block; + counter-increment: linenumber; +} +.line-numbers-rows > span:before { + content: counter(linenumber); + color: #b5b9c2; + display: block; + padding-right: 0.8em; + text-align: right; +} +code.language-css, +pre.languagecss { + color: #E45649; +} +code.language-javascript, +pre.languagejavascript { + color: #E45649; +} +code.language-js, +pre.languagejs { + color: #E45649; +} +code.language-jsx, +pre.languagejsx { + color: #E45649; +} +code.language-sass, +pre.languagesass { + color: #E45649; +} +code.language-scss, +pre.languagescss { + color: #E45649; +} +code.language-ts, +pre.languagets { + color: #E45649; +} +code.language-tsx, +pre.languagetsx { + color: #E45649; +} +code.language-typescript, +pre.languagetypescript { + color: #E45649; +} + +.dark pre[class*="language-"] { + color: var(--color-slate-50); +} +.dark .token.module { + color: var(--color-pink-400)!important; +} +.dark .token.attr-name, +.dark .token.keyword, +.dark .token.rule, +.dark .token.pseudo-class, +.dark .token.important { + color: var(--color-slate-300); +} +.dark .token.comment, +.dark .token.operator, +.dark .token.combinator { + color: var(--color-slate-400); +} +.dark .token.punctuation, +.dark .token.attr-equals { + color: var(--color-slate-500); +} +.dark .token.attr-value, +.dark .token.class, +.dark .token.string, +.dark .token.property { + color: var(--color-sky-300); +} \ No newline at end of file diff --git a/app/pages/communaute/partages/+Page.mdx b/app/pages/communaute/partages/+Page.mdx index 44d0a19..cdf4a22 100644 --- a/app/pages/communaute/partages/+Page.mdx +++ b/app/pages/communaute/partages/+Page.mdx @@ -33,9 +33,3 @@ Un grand merci à ces entités qui utilisent le contenu de Memento Dev pour leur Pour figurer sur cette page, tu peux tout simplement m'en faire la demande par [email _(gauthier@gauthierdaniels.fr)_](mailto:gauthier@gauthierdaniels?subject=Demande%20d'ajout%20sur%20la%20page%20partages%20et%20r%C3%A9utilisations%20Memento%20Dev). - -{% callout type="note" title="Tu utilises mon contenu et tu souhaites apparaître ici ?" %} - - - -{% /callout %} diff --git a/app/pages/docs/react/+Page.mdx b/app/pages/docs/react/+Page.mdx new file mode 100644 index 0000000..e7bb115 --- /dev/null +++ b/app/pages/docs/react/+Page.mdx @@ -0,0 +1,150 @@ +--- +title: Introduction à React +description: Parlons un peu de React, ce qu'il est, ce qu'il fait et pourquoi il est si populaire. +tags: [Frontend, React, JavaScript, TypeScript, Bibliothèque, Interface utilisateur (UI)] +--- + +import Callout from "@/components/Callout"; +import tabs from "./tabs"; + +Parlons peu, parlons bien ! 😄 + +React est une **bibliothèque** _(non, pas un **framework** !)_ JavaScript open-source développée par Facebook. +Elle est utilisée pour construire des interfaces utilisateur _(UI)_ interactives et dynamiques. + + + - **Facilité d'utilisation** : React est facile à apprendre et à utiliser. Il est basé sur JavaScript, qui est l'un des langages de programmation les plus populaires. + - **Réutilisabilité des composants** : React permet de créer des composants réutilisables. Cela signifie que tu peux créer un composant une fois et l'utiliser partout où tu en as besoin. + - **Performances** : React utilise un DOM virtuel _(Virtual DOM)_ pour améliorer les performances de l'application. + - **Communauté active** : React a une communauté active de développeurs qui contribuent à son développement et partagent des ressources utiles. + + +Mais on peut aussi y noter des points faibles bien entendu, car tout n'est pas rose : + +- **Courbe d'apprentissage** : Bien que React soit "facile" à apprendre, les concepts avancés demandent un peu de temps pour être maîtrisés. +- **Taille du bundle** : React est relativement lourd en termes de taille de bundle, ce qui peut affecter les performances de l'application en terme de chargement initial. +- **GAFAM** : Comme d'autres bibliothèques/frameworks, React n'échappe pas à la critique de la part de certains développeurs qui ne souhaitent pas utiliser des technologies développées par des géants du web. + +## 🤔 Pourquoi une bibliothèque et pas un framework ? + +Très grand débat que voilà ! Vraiment.. il y a des guerres qui se sont déclarées pour moins que ça 😅 + +Blague à part, pour pouvoir dire que React n'est pas un framework, il faut comprendre la différence entre les deux : + +- **Framework** : Un framework est un ensemble de bibliothèques et de composants qui sont prédéfinis et structurés pour te permettre de construire une application. + En gros, le framework te dit comment faire les choses. +- **Bibliothèque** : Une bibliothèque est un ensemble de fonctions et de composants que tu peux utiliser pour construire une application. + En gros, c'est toi qui décides comment faire les choses. + +Et si tu connais déjà React, je te vois venir avec tes grands sabots... ! + + + C'est vrai ! React a ses propres règles et conventions, mais il te laisse quand même une grande liberté pour organiser ton code comme tu le souhaites. + + Si on se concentre sur la **préoccupation principale** de React, c'est de gérer l'**interface utilisateur** _(UI)_ de ton application. + En aucun cas, React _(tel quel et "pour le moment")_ va te dire comment gérer ton état global, comment gérer tes requêtes HTTP, etc. + + Mais tu peux totalement utiliser React **au sein** d'un framework ! + Tu as notamment des frameworks comme [**Next.js**](https://nextjs.org/) ou [**Gatsby**](https://www.gatsbyjs.com/) qui utilisent React + avec des fonctionnalités supplémentaires pour gérer le routage, le rendu côté serveur, etc. + + _(Le meilleur, selon moi, c'est [**Vike**](https://vike.dev/) qui te permet d'utiliser presque n'importe quelle bibliothèque avec une même architecture 😏)_ + + Mais concentrons-nous sur React en tant que bibliothèque, et non en tant que framework 😉 + + +## 📝 JSX + +Ce qui peut être déroutant au premier abord avec React, c'est le **JSX**. +On serait tenté de dire que c'est du HTML, mais en fait : **pas du tout** ! + +Le JSX est un sucre syntaxique _(syntactic sugar)_ qui permet d'écrire du code JavaScript en se basant sur le système de balisage HTML. + +L'avantage de JSX c'est que le code devient beaucoup plus lisible et plus proche de ce que tu connais déjà avec HTML. +Mais il s'agit bien de JavaScript, et non de HTML ! + +## 🧩 Composants + +React est basé sur le concept de **composants**. Un composant est une partie réutilisable de l'interface utilisateur _(UI)_ qui peut être affichée à l'écran. + +Dans la majorité des cas, on va chercher à **mutualiser** les composants pour éviter de répéter du code inutilement. +L'exemple le plus flagrant sera par exemple tous les boutons de ton application, qui auront probablement tous la même apparence et le même comportement. + +Il est aussi possible de les **imbriquer** les uns dans les autres ! +En fait, on joue avec des Lego, mais en version code 👷 + +Mais si on veut vraiment montrer le potentiel de React, parlons maintenant... 🥁 +Des **states**, **cycles de vie** et des **props** ! + +## 🛠️ States, Cycles de vie et Props + +T'assomer aussi vite avec ces termes qui ne te parlent peut-être pas, c'est pas cool de ma part... +Pardon pour les gros mots, je me calme tout de suite ! 🙈 + +Si ça te rassure, je vais très rapidement évoquer ce qu'il se cache derrière ces termes barbares, +je réserve les détails pour des articles dédiés 😉 + +### 🗄️ States + +... ou également appelés **états** en français. + +Le but du state, c'est de stocker des données qui vont être **observées** par React. +À chaque fois que le state va être modifié, React va **réagir** et mettre à jour l'interface utilisateur _(UI)_ en conséquence afin d'afficher les nouvelles données. + +### 🔄 Cycles de vie + +Les **cycles de vie** _(lifecycle)_ sont des méthodes qui sont appelées à des moments précis dans le cycle de vie d'un composant React. + +Si tu as lu la section qui parle brièvement des states, tu auras peut-être remarqué cette phrase : + +> À chaque fois que le state va être modifié, React va **réagir** et mettre à jour l'interface utilisateur [...] + +Et bien c'est là que les cycles de vie entrent en jeu ! +Un composant sur React va avoir un cycle de vie, caractérisé par trois phases : + +1. **Montage du composant** _(Mounting)_ : le composant est créé et inséré dans le DOM. +2. **Mise à jour du composant** _(Updating)_ : le composant est mis à jour en fonction des changements de state ou de props. +3. **Démontage du composant** _(Unmounting)_ : le composant est retiré du DOM. + +Ces différentes phases vont nous permettre d'interagir avec le composant à des moments précis, et d'effectuer des actions en conséquence. + +### 📦 Props + +Et pour finir, les **props** _(properties ou tout simplement "propriétés" en français)_ ! + +Il s'agit ni plus ni moins que des **arguments** que tu vas passer à un composant, comme tu le ferais avec une fonction. + +Cependant il faut noter une chose : + +On transmet les props à un composant précis, qui sera donc un composant **enfant**. +Un composant enfant ne pourra pas transmettre des props à un composant parent, c'est unidirectionnel _(mais on verra comment on peut faire autrement 😉)_. + +## 🖥️ Une petite démo ? + +OK, mais vraiment petite ! + +Prenons l'exemple d'une application qui servira **uniquement** à afficher une liste de tâches _(une todolist donc !)_. +_(Bon... utiliser React uniquement pour ça c'est abusé, mais c'est pour l'exemple 😅)_ + + + +On peut très bien imaginer des améliorations à cette application, comme par exemple : + +- Supprimer une tâche +- Réinitialiser la liste des tâches +- Marquer une tâche comme terminée _(et inversement)_ +- Ordonner les tâches pour afficher en priorité les tâches non terminées +- Enregistrer les tâches dans le navigateur pour les retrouver après un rafraîchissement de la page + +Et si on se gardait ça pour la suite ? 😉 + +## Conclusion + +Tu l'auras compris, React permet de résoudre un certain nombre de problématiques que l'on peut rencontrer lors du développement d'une application web. + +Pas des problématiques majeures, mais ça nous permet tout de même en tant que développeur de gagner du temps et de l'efficacité ! + +Dans le cas où le fait que ce soit créé et maintenu par Facebook _(ou GAFAM de manière générale)_ est contre tes valeurs, +tu as des solutions très semblables qui existent, comme [**SolidJS**](https://www.solidjs.com/) par exemple. + +Et si tu veux en savoir plus, je t'invite à lire les articles suivants qui vont te permettre de rentrer un peu plus dans le détail de React ! 🚀 diff --git a/app/pages/docs/react/tabs.tsx b/app/pages/docs/react/tabs.tsx new file mode 100644 index 0000000..3205291 --- /dev/null +++ b/app/pages/docs/react/tabs.tsx @@ -0,0 +1,94 @@ +import { Highlight } from "@/components/Highlight"; +import Tabs from "@/components/Tabs"; + +export default { + reactTodolist: () => { + return ( + + + + {`import TodoList from "./TodoList"; +import React from "react"; + +const App = () => { + return ( +
+

TodoList

+ + +
+ ); +};`} +
+
+ + + {` + \`\`\`tsx showLineNumbers + import TodoListItem from "./TodoListItem"; + import React from "react"; + + const TodoList = () => { + const [items, setItems] = React.useState([]); + const [inputValue, setInputValue] = React.useState(""); + + const handleInputValueChange = (event: React.ChangeEvent) => { + setInputValue(event.target.value); + }; + + const handleSubmit = (event: React.FormEvent) => { + // On empêche le comportement par défaut du formulaire + event.preventDefault(); + + // On ajoute un nouvel élément à la liste des tâches + setItems([...items, inputValue]); + + // On réinitialise la valeur de l'input + setInputValue(""); + }; + + return ( +
+
+ + + +
+ +
    + {items.map((item, index) => ( +
  • + +
  • + ))} +
+
+ ); + }; + + export default TodoList; + \`\`\` + `} +
+ + + {` + \`\`\`tsx showLineNumbers + import React from "react"; + + interface TodoListItemProps { + item: string; + } + + const TodoListItem = (props: TodoListItemProps) => { + return {props.item}; + }; + + export default TodoListItem; + \`\`\` + `} + +
+ ); + }, +}; diff --git a/app/partials/HeroSection.tsx b/app/partials/HeroSection.tsx index d617087..143334c 100644 --- a/app/partials/HeroSection.tsx +++ b/app/partials/HeroSection.tsx @@ -2,26 +2,35 @@ import type { JSX } from "solid-js"; import blurIndigoImage from "@/images/blur-indigo.webp"; import blurCyanImage from "@/images/blur-cyan.webp"; -import { Highlight } from "@/components/Highlight"; import { HeroBackground } from "./HeroBackground"; +import { Snippet } from "@/components/Snippet"; import { Button } from "@/components/Button"; import { Image } from "@/components/Image"; -import { For } from "solid-js"; -import clsx from "clsx"; -const codeLanguage = "javascript"; -const code = `export default { - role: 'developer', - qualifications: [ - 'DWWM', - 'CDA', - 'CDUI', - ] -}`; - -const tabs = [ - { name: "memento-dev.config.js", isActive: true }, - { name: "package.json", isActive: false }, +const snippets = [ + { + name: "memento-dev.config.js", + codeLanguage: "javascript", + code: `export default { + role: "developer", + qualifications: [ + "DWWM", + "CDA", + "CDUI", + ] +}`, + }, + { + name: "package.json", + codeLanguage: "json", + code: `{ + "name": "memento-dev", + "version": "2.0.0", + "description": "Memento Dev est une plateforme open-source, soutenue et maintenue par une communauté de contributeurs passionnés.", + "main": "index.ts", + "license": "MIT" +}`, + }, ]; function TrafficLightsIcon(props: JSX.IntrinsicElements["svg"]) { @@ -87,55 +96,8 @@ export function HeroSection() { />
-
-
-
-
- -
- - {(tab) => ( -
-
- {tab.name} -
-
- )} -
-
-
- - {code} -
-
-
+
diff --git a/app/partials/Navigation.tsx b/app/partials/Navigation.tsx index a763a07..95365f5 100644 --- a/app/partials/Navigation.tsx +++ b/app/partials/Navigation.tsx @@ -58,7 +58,7 @@ function NavigationItem(props: NavigationItemProps) { {isOpened() && ( -
    +
      {(link) => (