Cours JavaScript et React · Module 7: Styles et animations

Sass (SCSS) - Le super-pouvoir syntaxique cosmique

13 min de lecture
Dans cette leçon9

Dans le cosmos du style React, Sass (Syntactically Awesome Style Sheets) est comme un vaisseau de nouvelle génération : il offre toutes les capacités du CSS standard, mais avec des pouvoirs supplémentaires qui rendent le style plus efficace, plus modulaire et plus facile à maintenir. En naviguant dans la galaxie React, il vaut la peine de maîtriser cette technologie qui peut considérablement simplifier la création de styles avancés.

Qu'est-ce que Sass/SCSS ?

Sass est un préprocesseur CSS qui étend le CSS standard avec des variables, des règles imbriquées, des mixins, l'héritage de sélecteurs et de nombreuses autres fonctionnalités. Il existe deux syntaxes Sass :

  1. Syntaxe indentée (.sass) - basée sur l'indentation, sans accolades ni points-virgules
  2. SCSS (.scss) - syntaxe plus récente qui est un sur-ensemble du CSS (tout CSS valide est un SCSS valide)

Dans ce module, nous nous concentrerons sur SCSS, car il est plus populaire et plus facile à apprendre pour les personnes qui connaissent le CSS. Imagine SCSS comme un carburant de fusée amélioré qui conserve toutes les propriétés du carburant standard mais ajoute plus de puissance.

Mettre en place Sass dans un projet React

Pour utiliser Sass dans un projet React, nous devons d'abord installer les paquets appropriés :

Create React App

Si tu utilises Create React App, l'installation est simple :

1npm install sass

C'est tout ! CRA prend en charge Sass nativement, donc après l'installation tu peux simplement créer des fichiers .scss/.sass et les importer dans tes composants.

Vite

Dans les projets Vite, le processus est similaire :

1npm install sass

Vite prend en charge Sass nativement, donc aucune configuration supplémentaire n'est nécessaire.

Autres configurations (par exemple, Webpack personnalisé)

Si tu crées ta propre configuration, tu as besoin de :

1npm install sass sass-loader

Et ajouter la configuration appropriée au fichier webpack.config.js :

1// Configuration webpack simplifiée
2module.exports = {
3  //
4  module: {
5    rules: [
6      {
7        test: /\.s[ac]ss$/i,
8        use: [
9          'style-loader',
10          'css-loader',
11          'sass-loader',
12        ],
13      },
14    ],
15  },
16};

Utiliser SCSS dans un projet React

Une fois configuré, tu peux créer des fichiers SCSS et les importer dans tes composants :

1// SpacePanel.jsx
2import React from 'react';
3import './SpacePanel.scss'; // On importe le SCSS au lieu du CSS
4
5function SpacePanel() {
6  return (
7    <div className="space-panel">
8      <h2 className="panel-title">Ship Control Panel</h2>
9      <div className="control-area">
10        <div className="button-group">
11          <button className="control-button primary">Start engines</button>
12          <button className="control-button secondary">Navigation</button>
13        </div>
14        <div className="status-display">Status: Ready for launch</div>
15      </div>
16    </div>
17  );
18}
19
20export default SpacePanel;

Fonctionnalités clés de SCSS

1. Les variables

Dans une interface cosmique, nous utilisons souvent les mêmes couleurs, marges ou polices. Les variables SCSS nous permettent de les définir une fois et de les réutiliser autant de fois que nécessaire :

1// variables.scss
2$primary-color: #8a2be2;      // Violet cosmique
3$secondary-color: #4a90e2;    // Bleu spatial
4$danger-color: #ff4d4f;       // Alerte rouge
5$success-color: #52c41a;      // Vert « go »
6
7$border-radius-small: 4px;
8$border-radius-medium: 8px;
9$border-radius-large: 16px;
10
11$font-main: 'Space Grotesk', sans-serif;
12$font-display: 'Nova Mono', monospace;
13
14// Utilisation des variables
15.control-button {
16  background-color: $secondary-color;
17  border-radius: $border-radius-medium;
18  font-family: $font-main;
19
20  &.primary {
21    background-color: $primary-color;
22  }
23
24  &.danger {
25    background-color: $danger-color;
26  }
27}

2. L'imbrication des sélecteurs

L'imbrication des sélecteurs permet de créer une hiérarchie plus lisible qui reflète la structure HTML :

1.space-panel {
2  background-color: #1a1a2e;
3  padding: 20px;
4  border-radius: 10px;
5
6  .panel-title {
7    color: white;
8    font-size: 24px;
9    margin-bottom: 15px;
10  }
11
12  .control-area {
13    display: flex;
14    justify-content: space-between;
15
16    .button-group {
17      display: flex;
18      gap: 10px;
19
20      .control-button {
21        padding: 10px 15px;
22        // Autres styles de bouton...
23      }
24    }
25
26    .status-display {
27      background-color: rgba(0, 0, 0, 0.3);
28      padding: 10px;
29      border-radius: 5px;
30    }
31  }
32}

Cette notation est non seulement plus lisible, mais elle aide aussi à éviter la duplication de sélecteurs et rend la structure plus évidente.

3. Le sélecteur & (esperluette)

L'esperluette (&) représente le sélecteur courant et est particulièrement utile pour :

Pseudo-classes et pseudo-éléments

1.control-button {
2  background-color: #4a90e2;
3  transition: all 0.3s;
4
5  &:hover {
6    background-color: darken(#4a90e2, 10%);
7    transform: translateY(-2px);
8  }
9
10  &:active {
11    transform: translateY(1px);
12  }
13
14  &::before {
15    content: "▲";
16    margin-right: 5px;
17  }
18}

Modificateurs de classe

1.control-button {
2  // Styles de base...
3
4  &.primary {
5    background-color: #8a2be2;
6  }
7
8  &.secondary {
9    background-color: #4a90e2;
10  }
11
12  &.large {
13    padding: 12px 20px;
14    font-size: 18px;
15  }
16}

Combiner les sélecteurs

1.space-theme {
2  .control-button {
3    // Styles pour .space-theme .control-button
4  }
5
6  // Avec & on peut sélectionner différentes combinaisons de sélecteurs
7  &__dark .control-button {
8    // Styles pour .space-theme__dark .control-button
9  }
10}

4. Les mixins - Code réutilisable

Les mixins sont des ensembles de déclarations CSS qui peuvent être utilisés plusieurs fois dans le code. Ce sont comme des modules de vaisseau réutilisables :

1// mixins.scss
2@mixin flex-center {
3  display: flex;
4  justify-content: center;
5  align-items: center;
6}
7
8@mixin glass-effect {
9  background: rgba(255, 255, 255, 0.1);
10  backdrop-filter: blur(10px);
11  border: 1px solid rgba(255, 255, 255, 0.2);
12  box-shadow: 0 8px 32px rgba(31, 38, 135, 0.2);
13}
14
15@mixin button-variant($bg-color, $text-color) {
16  background-color: $bg-color;
17  color: $text-color;
18
19  &:hover {
20    background-color: darken($bg-color, 10%);
21  }
22
23  &:active {
24    background-color: darken($bg-color, 15%);
25  }
26}

Utilisation des mixins :

1.control-panel {
2  @include glass-effect;
3  padding: 20px;
4}
5
6.button-container {
7  @include flex-center;
8  gap: 10px;
9}
10
11.launch-button {
12  @include button-variant(#ff4d4f, white);
13  padding: 10px 20px;
14}
15
16.navigation-button {
17  @include button-variant(#4a90e2, white);
18  padding: 8px 15px;
19}

5. L'héritage - @extend

L'héritage permet à une classe d'hériter des propriétés d'une autre, réduisant ainsi la duplication de code :

1.base-button {
2  padding: 10px 15px;
3  border: none;
4  border-radius: 4px;
5  font-weight: bold;
6  cursor: pointer;
7  transition: all 0.3s;
8}
9
10.primary-button {
11  @extend .base-button;
12  background-color: #8a2be2;
13  color: white;
14}
15
16.secondary-button {
17  @extend .base-button;
18  background-color: #4a90e2;
19  color: white;
20}
21
22.ghost-button {
23  @extend .base-button;
24  background-color: transparent;
25  color: #8a2be2;
26  border: 1px solid currentColor;
27}

6. Fonctions mathématiques et de couleur

SCSS offre de nombreuses fonctions intégrées pour manipuler les couleurs et les valeurs :

1.panel {
2  // Opérations mathématiques
3  width: 100% - 40px;
4  padding: 10px * 2;
5
6  // Fonctions de couleur
7  background-color: lighten(#1a1a2e, 10%);
8  border: 1px solid darken(#1a1a2e, 5%);
9
10  .warning-label {
11    color: transparentize(#ff4d4f, 0.2); // Ajoute de la transparence
12  }
13
14  .accent-element {
15    background-color: mix(#8a2be2, #4a90e2, 30%); // Mélange les couleurs
16  }
17}

7. Import et organisation des fichiers

SCSS permet de découper le code en fichiers modulaires, ce qui est crucial pour les grands projets :

1// styles/variables.scss
2$primary-color: #8a2be2;
3// Autres variables...
4
5// styles/mixins.scss
6@mixin flex-center { /* ... */ }
7// Autres mixins...
8
9// styles/buttons.scss
10@import 'variables';
11@import 'mixins';
12
13.button { /* ... */ }
14// Styles de bouton...
15
16// styles/panels.scss
17@import 'variables';
18@import 'mixins';
19
20.panel { /* ... */ }
21// Styles de panneau...
22
23// styles/main.scss
24@import 'variables';
25@import 'mixins';
26@import 'buttons';
27@import 'panels';
28// On peut tout importer ensemble ou de façon sélective dans les composants

8. Opérateurs conditionnels et boucles

SCSS propose aussi des structures de contrôle connues des langages de programmation :

1// Conditions
2$theme: 'dark';
3
4.space-control {
5  @if $theme == 'dark' {
6    background-color: #121212;
7    color: white;
8  } @else {
9    background-color: #f5f5f5;
10    color: #333;
11  }
12}
13
14// Boucles
15$sizes: (
16  'small': 8px,
17  'medium': 16px,
18  'large': 24px,
19  'xlarge': 32px
20);
21
22@each $name, $size in $sizes {
23  .margin-#{$name} {
24    margin: $size;
25  }
26
27  .padding-#{$name} {
28    padding: $size;
29  }
30}
31
32// Génère :
33// .margin-small { margin: 8px; }
34// .padding-small { padding: 8px; }
35// ...etc. pour toutes les tailles

Intégrer SCSS avec les CSS Modules dans React

SCSS peut se combiner parfaitement avec les CSS Modules, créant une solution de style puissante :

1// SpacePanel.module.scss
2@import 'src/styles/variables';
3@import 'src/styles/mixins';
4
5.panel {
6  @include glass-effect;
7  background-color: $panel-bg-color;
8
9  .title {
10    color: $text-primary;
11    font-family: $font-display;
12  }
13
14  .controls {
15    display: flex;
16    gap: $spacing-md;
17
18    .button {
19      @include button-base;
20
21      &.primary {
22        @include button-variant($primary-color, white);
23      }
24
25      &.secondary {
26        @include button-variant($secondary-color, white);
27      }
28    }
29  }
30}

Utilisation dans un composant :

1import React from 'react';
2import styles from './SpacePanel.module.scss';
3
4function SpacePanel() {
5  return (
6    <div className={styles.panel}>
7      <h2 className={styles.title}>Control Panel</h2>
8      <div className={styles.controls}>
9        <button className={`${styles.button} ${styles.primary}`}>
10          Launch
11        </button>
12        <button className={`${styles.button} ${styles.secondary}`}>
13          Cancel
14        </button>
15      </div>
16    </div>
17  );
18}

Bonnes pratiques pour utiliser SCSS dans React

1. Structure des fichiers

Organise tes fichiers SCSS d'une façon qui reflète la structure des composants :

1src/
2├── styles/                    # Global styles and assets
3│   ├── _variables.scss        # Convention : les fichiers avec underscore sont réservés à l'import
4│   ├── _mixins.scss
5│   ├── _functions.scss
6│   ├── _reset.scss
7│   └── global.scss            # Fichier principal des styles globaux
8├── components/
9│   ├── Button/
10│   │   ├── Button.jsx
11│   │   └── Button.module.scss
12│   ├── Panel/
13│   │   ├── Panel.jsx
14│   │   └── Panel.module.scss
15│   └──
16└──

2. Conventions de nommage

Utilise des conventions de nommage cohérentes :

  • BEM (Block, Element, Modifier) - pour les classes CSS
  • camelCase - pour les variables et mixins SCSS
  • PascalCase - pour les composants React
  • kebab-case - pour les fichiers SCSS
1// _variables.scss
2$primaryColor: #8a2be2;
3$secondaryColor: #4a90e2;
4
5// Button.module.scss (using BEM)
6.button {
7  // Bloc
8  background-color: $primaryColor;
9
10  &__icon {
11    // Élément
12    margin-right: 8px;
13  }
14
15  &--large {
16    // Modificateur
17    padding: 12px 24px;
18  }
19}

3. Évite l'imbrication excessive

Une imbrication trop profonde peut conduire à des sélecteurs trop spécifiques, plus difficiles à surcharger :

1// À éviter :
2.panel {
3  .content {
4    .section {
5      .title {
6        .icon {
7          // 5 niveaux d'imbrication !
8          color: red;
9        }
10      }
11    }
12  }
13}
14
15// Mieux :
16.panel {
17  // Styles généraux du panneau
18}
19
20.panel-content {
21  // Styles du contenu
22}
23
24.section-title-icon {
25  color: red;
26}

4. Utilise des variables pour les valeurs répétées

1// _spacing.scss
2$spacing-xs: 4px;
3$spacing-sm: 8px;
4$spacing-md: 16px;
5$spacing-lg: 24px;
6$spacing-xl: 32px;
7
8// _colors.scss
9$blue-100: #e3f2fd;
10$blue-200: #bbdefb;
11//
12$blue-900: #0d47a1;
13
14$color-primary: $blue-500;
15$color-secondary: $purple-500;

5. Crée des mixins systèmes

1@mixin media-breakpoint-up($breakpoint) {
2  @if $breakpoint == sm {
3    @media (min-width: 576px) { @content; }
4  } @else if $breakpoint == md {
5    @media (min-width: 768px) { @content; }
6  } @else if $breakpoint == lg {
7    @media (min-width: 992px) { @content; }
8  } @else if $breakpoint == xl {
9    @media (min-width: 1200px) { @content; }
10  }
11}
12
13// Utilisation :
14.responsive-element {
15  font-size: 14px;
16
17  @include media-breakpoint-up(md) {
18    font-size: 16px;
19  }
20
21  @include media-breakpoint-up(lg) {
22    font-size: 18px;
23  }
24}

Exemple pratique : Panneau de contrôle de vaisseau

Voyons comment appliquer SCSS pour créer un panneau de contrôle interactif :

1// _variables.scss
2$primary-color: #7b2cbf;
3$secondary-color: #3a86ff;
4$success-color: #06d6a0;
5$warning-color: #ffd166;
6$danger-color: #ef476f;
7$dark-bg: #1a1a2e;
8$panel-bg: #252941;
9$text-light: #e6e6ff;
10
11$border-radius-sm: 4px;
12$border-radius-md: 8px;
13$border-radius-lg: 12px;
14
15$transition-fast: 0.2s ease;
16$transition-normal: 0.3s ease;
17
18// _mixins.scss
19@mixin glass-morphism {
20  background: rgba(37, 41, 65, 0.8);
21  backdrop-filter: blur(10px);
22  border: 1px solid rgba(255, 255, 255, 0.1);
23  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
24}
25
26@mixin button-glow($color) {
27  box-shadow: 0 0 15px rgba($color, 0.5);
28  &:hover {
29    box-shadow: 0 0 25px rgba($color, 0.7);
30  }
31}
32
33// SpaceControlPanel.module.scss
34@import 'src/styles/variables';
35@import 'src/styles/mixins';
36
37.controlPanel {
38  @include glass-morphism;
39  padding: 24px;
40  border-radius: $border-radius-lg;
41  color: $text-light;
42
43  .panelHeader {
44    display: flex;
45    justify-content: space-between;
46    align-items: center;
47    margin-bottom: 24px;
48    padding-bottom: 16px;
49    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
50
51    .title {
52      font-size: 28px;
53      font-weight: 600;
54
55      &::before {
56        content: "▲";
57        margin-right: 10px;
58      }
59    }
60
61    .status {
62      padding: 6px 12px;
63      border-radius: $border-radius-sm;
64      font-size: 14px;
65      font-weight: 500;
66
67      &.ready {
68        background-color: rgba($success-color, 0.2);
69        color: $success-color;
70      }
71
72      &.standby {
73        background-color: rgba($warning-color, 0.2);
74        color: $warning-color;
75      }
76
77      &.error {
78        background-color: rgba($danger-color, 0.2);
79        color: $danger-color;
80      }
81    }
82  }
83
84  .controlGrid {
85    display: grid;
86    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
87    gap: 20px;
88
89    .controlSection {
90      background-color: rgba(0, 0, 0, 0.2);
91      border-radius: $border-radius-md;
92      padding: 16px;
93
94      .sectionTitle {
95        font-size: 18px;
96        margin-bottom: 16px;
97        color: rgba($text-light, 0.8);
98      }
99
100      .buttonGroup {
101        display: flex;
102        flex-direction: column;
103        gap: 12px;
104      }
105    }
106  }
107
108  .button {
109    padding: 12px 16px;
110    border: none;
111    border-radius: $border-radius-sm;
112    font-weight: 500;
113    letter-spacing: 0.5px;
114    cursor: pointer;
115    transition: all $transition-normal;
116    position: relative;
117    overflow: hidden;
118
119    &::before {
120      content: "";
121      position: absolute;
122      top: 0;
123      left: -100%;
124      width: 100%;
125      height: 100%;
126      background: linear-gradient(
127        90deg,
128        transparent,
129        rgba(255, 255, 255, 0.2),
130        transparent
131      );
132      transition: all 0.6s;
133    }
134
135    &:hover::before {
136      left: 100%;
137    }
138
139    &.primary {
140      background-color: $primary-color;
141      color: white;
142      @include button-glow($primary-color);
143    }
144
145    &.secondary {
146      background-color: $secondary-color;
147      color: white;
148      @include button-glow($secondary-color);
149    }
150
151    &.success {
152      background-color: $success-color;
153      color: black;
154      @include button-glow($success-color);
155    }
156
157    &.warning {
158      background-color: $warning-color;
159      color: black;
160      @include button-glow($warning-color);
161    }
162
163    &.danger {
164      background-color: $danger-color;
165      color: white;
166      @include button-glow($danger-color);
167    }
168
169    &.disabled {
170      opacity: 0.5;
171      cursor: not-allowed;
172      pointer-events: none;
173    }
174  }
175
176  .monitorDisplay {
177    margin-top: 24px;
178    padding: 16px;
179    background-color: rgba(0, 0, 0, 0.4);
180    border-radius: $border-radius-md;
181    font-family: monospace;
182    height: 120px;
183    overflow-y: auto;
184
185    .logLine {
186      margin: 4px 0;
187      font-size: 14px;
188
189      &.info {
190        color: $secondary-color;
191      }
192
193      &.success {
194        color: $success-color;
195      }
196
197      &.warning {
198        color: $warning-color;
199      }
200
201      &.error {
202        color: $danger-color;
203      }
204
205      &::before {
206        content: "> ";
207      }
208    }
209  }
210}
211
212// Utilisation dans un composant React
213import React, { useState } from 'react';
214import styles from './SpaceControlPanel.module.scss';
215
216function SpaceControlPanel() {
217  const [status, setStatus] = useState('standby');
218  const [logs, setLogs] = useState([
219    { type: 'info', text: 'System initialized' },
220    { type: 'info', text: 'Waiting for commands' }
221  ]);
222
223  const addLog = (text, type = 'info') => {
224    setLogs(prev => [...prev, { type, text }]);
225  };
226
227  const initiateSequence = () => {
228    setStatus('ready');
229    addLog('Launch sequence initiated', 'success');
230  };
231
232  const abortSequence = () => {
233    setStatus('standby');
234    addLog('Launch sequence aborted', 'warning');
235  };
236
237  const triggerAlert = () => {
238    setStatus('error');
239    addLog('ALERT: Critical system failure detected', 'error');
240  };
241
242  return (
243    <div className={styles.controlPanel}>
244      <div className={styles.panelHeader}>
245        <h2 className={styles.title}>Cosmic Control Panel</h2>
246        <div className={`${styles.status} ${styles[status]}`}>
247          Status: {status === 'standby' ? 'Standby' :
248                  status === 'ready' ? 'Ready' : 'Error'}
249        </div>
250      </div>
251
252      <div className={styles.controlGrid}>
253        <div className={styles.controlSection}>
254          <h3 className={styles.sectionTitle}>Launch Control</h3>
255          <div className={styles.buttonGroup}>
256            <button
257              className={`${styles.button} ${styles.success}`}
258              onClick={initiateSequence}
259            >
260              Initiate Launch
261            </button>
262            <button
263              className={`${styles.button} ${styles.warning}`}
264              onClick={abortSequence}
265            >
266              Abort Sequence
267            </button>
268          </div>
269        </div>
270
271        <div className={styles.controlSection}>
272          <h3 className={styles.sectionTitle}>Onboard Systems</h3>
273          <div className={styles.buttonGroup}>
274            <button
275              className={`${styles.button} ${styles.primary}`}
276              onClick={() => addLog('Navigation systems online', 'info')}
277            >
278              Navigation System
279            </button>
280            <button
281              className={`${styles.button} ${styles.secondary}`}
282              onClick={() => addLog('Life support activated', 'info')}
283            >
284              Life Support
285            </button>
286          </div>
287        </div>
288
289        <div className={styles.controlSection}>
290          <h3 className={styles.sectionTitle}>Safety</h3>
291          <div className={styles.buttonGroup}>
292            <button
293              className={`${styles.button} ${styles.danger}`}
294              onClick={triggerAlert}
295            >
296              Emergency Alert
297            </button>
298            <button
299              className={`${styles.button} ${styles.disabled}`}
300            >
301              Evacuation Protocol
302            </button>
303          </div>
304        </div>
305      </div>
306
307      <div className={styles.monitorDisplay}>
308        {logs.map((log, index) => (
309          <div key={index} className={`${styles.logLine} ${styles[log.type]}`}>
310            {log.text}
311          </div>
312        ))}
313      </div>
314    </div>
315  );
316}
317
318export default SpaceControlPanel;

Sass vs CSS-in-JS : lequel utiliser ?

Avantages de SCSS

  1. Performance - le CSS est généré au moment du build, pas à l'exécution
  2. Syntaxe familière - très proche du CSS standard
  3. Support des outils - prise en charge complète des outils de développement (DevTools)
  4. Extraction du CSS - les styles peuvent être extraits dans des fichiers CSS séparés
  5. Taille de bundle réduite - ne nécessite pas de bibliothèques JS supplémentaires

Quand choisir SCSS ?

  • Dans les grands projets aux styles complexes
  • Quand tu préfères séparer la logique et le style
  • Quand la performance est critique
  • Quand tu travailles avec des designers qui utilisent du CSS/SCSS traditionnel
  • Quand l'équipe a de l'expérience avec SCSS

Résumé

Sass (SCSS) est un outil puissant qui étend considérablement les capacités du CSS tout en restant proche de sa syntaxe d'origine. Dans l'écosystème React, SCSS s'intègre bien à la fois avec l'approche modulaire (CSS Modules) et avec d'autres techniques de style.

Grâce aux variables, aux mixins, à l'imbrication et à d'autres fonctionnalités, SCSS permet de créer un code CSS plus modulaire et plus facile à maintenir, ce qui est particulièrement important dans les grandes applications React.

Comme tout vaisseau avancé, SCSS demande un peu de temps à maîtriser, mais l'investissement dans l'apprentissage est vite rentabilisé sous la forme d'un travail plus efficace et d'un code mieux organisé. Pour les applications React, où la gestion des composants et de leurs styles est cruciale, SCSS est l'une des meilleures options de style, offrant un excellent équilibre entre puissance et simplicité.

Code de cette leçon : App.jsx
1import React, { useState } from 'react';
2import styled, { ThemeProvider, createGlobalStyle, keyframes } from 'styled-components';
3
4// Définition des thèmes
5const themes = {
6  space: {
7    primary: '#00d4ff',
8    secondary: '#ffd700',
9    background: 'linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)',
10    cardBg: 'rgba(255, 255, 255, 0.05)',
11    text: '#ffffff',
12    textSecondary: '#a0a0a0',
13  },
14  nebula: {
15    primary: '#ff6b9d',
16    secondary: '#c44569',
17    background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)',
18    cardBg: 'rgba(255, 107, 157, 0.1)',
19    text: '#ffffff',
20    textSecondary: '#b0b0b0',
21  },
22  solar: {
23    primary: '#ffa502',
24    secondary: '#ff6348',
25    background: 'linear-gradient(135deg, #2c2c54 0%, #474787 50%, #706fd3 100%)',
26    cardBg: 'rgba(255, 165, 2, 0.1)',
27    text: '#ffffff',
28    textSecondary: '#c0c0c0',
29  },
30};
31
32// Global styles
33const GlobalStyle = createGlobalStyle`
34  * {
35    margin: 0;
36    padding: 0;
37    box-sizing: border-box;
38  }
39
40  body {
41    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
42    min-height: 100vh;
43  }
44`;
45
46// Animations
47const float = keyframes`
48  0%, 100% { transform: translateY(0); }
49  50% { transform: translateY(-10px); }
50`;
51
52const pulse = keyframes`
53  0%, 100% { opacity: 1; }
54  50% { opacity: 0.7; }
55`;
56
57// Styled components
58const AppContainer = styled.div`
59  min-height: 100vh;
60  background: ${props => props.theme.background};
61  padding: 30px;
62  color: ${props => props.theme.text};
63  transition: all 0.5s ease;
64`;
65
66const Header = styled.header`
67  text-align: center;
68  margin-bottom: 40px;
69`;
70
71const Title = styled.h1`
72  font-size: 2.5em;
73  color: ${props => props.theme.primary};
74  text-shadow: 0 0 20px ${props => props.theme.primary}40;
75  animation: ${float} 3s ease-in-out infinite;
76`;
77
78const Subtitle = styled.p`
79  color: ${props => props.theme.textSecondary};
80  margin-top: 10px;
81`;
82
83const ThemeSelector = styled.div`
84  display: flex;
85  justify-content: center;
86  gap: 15px;
87  margin-bottom: 40px;
88`;
89
90const ThemeButton = styled.button`
91  padding: 12px 24px;
92  border: 2px solid ${props => props.active ? props.theme.primary : 'transparent'};
93  border-radius: 8px;
94  background: ${props => props.theme.cardBg};
95  color: ${props => props.theme.text};
96  cursor: pointer;
97  font-weight: bold;
98  transition: all 0.3s ease;
99
100  &:hover {
101    background: ${props => props.theme.primary}30;
102    transform: translateY(-2px);
103  }
104`;
105
106const CardsGrid = styled.div`
107  display: grid;
108  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
109  gap: 25px;
110  max-width: 1200px;
111  margin: 0 auto;
112`;
113
114const Card = styled.div`
115  background: ${props => props.theme.cardBg};
116  border-radius: 15px;
117  padding: 25px;
118  border: 1px solid ${props => props.theme.primary}30;
119  transition: all 0.3s ease;
120
121  &:hover {
122    transform: translateY(-5px);
123    box-shadow: 0 10px 30px ${props => props.theme.primary}20;
124  }
125`;
126
127const CardTitle = styled.h3`
128  color: ${props => props.theme.secondary};
129  margin-bottom: 15px;
130  font-size: 1.4em;
131`;
132
133const CardText = styled.p`
134  color: ${props => props.theme.textSecondary};
135  line-height: 1.6;
136  margin-bottom: 20px;
137`;
138
139const Button = styled.button`
140  padding: 10px 20px;
141  background: ${props => props.variant === 'primary'
142    ? props.theme.primary
143    : 'transparent'};
144  color: ${props => props.variant === 'primary'
145    ? '#000'
146    : props.theme.primary};
147  border: 2px solid ${props => props.theme.primary};
148  border-radius: 6px;
149  cursor: pointer;
150  font-weight: bold;
151  transition: all 0.3s ease;
152
153  &:hover {
154    background: ${props => props.theme.primary};
155    color: #000;
156    transform: scale(1.05);
157  }
158
159  &:disabled {
160    opacity: 0.5;
161    cursor: not-allowed;
162  }
163`;
164
165const StatusBadge = styled.span`
166  display: inline-block;
167  padding: 4px 12px;
168  border-radius: 20px;
169  font-size: 0.85em;
170  font-weight: bold;
171  background: ${props => {
172    switch(props.status) {
173      case 'active': return 'rgba(76, 175, 80, 0.2)';
174      case 'pending': return 'rgba(255, 152, 0, 0.2)';
175      case 'inactive': return 'rgba(244, 67, 54, 0.2)';
176      default: return 'rgba(255, 255, 255, 0.1)';
177    }
178  }};
179  color: ${props => {
180    switch(props.status) {
181      case 'active': return '#4CAF50';
182      case 'pending': return '#ff9800';
183      case 'inactive': return '#f44336';
184      default: return '#fff';
185    }
186  }};
187  animation: ${props => props.status === 'active' ? pulse : 'none'} 2s infinite;
188`;
189
190const Footer = styled.footer`
191  text-align: center;
192  margin-top: 50px;
193  color: ${props => props.theme.textSecondary};
194  padding: 20px;
195  border-top: 1px solid ${props => props.theme.primary}20;
196`;
197
198// Composants de données
199const missions = [
200  {
201    id: 1,
202    name: 'Exploration de Mars',
203    description: 'Mission de recherche à la surface de la planète rouge',
204    status: 'active',
205  },
206  {
207    id: 2,
208    name: 'Station orbitale',
209    description: "Construction d'une nouvelle station spatiale en orbite terrestre",
210    status: 'pending',
211  },
212  {
213    id: 3,
214    name: 'Sonde jovienne',
215    description: "Étude de l'atmosphère de la plus grande planète du système solaire",
216    status: 'inactive',
217  },
218];
219
220function App() {
221  const [currentTheme, setCurrentTheme] = useState('space');
222
223  return (
224    <ThemeProvider theme={themes[currentTheme]}>
225      <GlobalStyle />
226      <AppContainer>
227        <Header>
228          <Title>Styled Components Demo</Title>
229          <Subtitle>Stylisation dynamique avec thèmes dans React</Subtitle>
230        </Header>
231
232        <ThemeSelector>
233          {Object.keys(themes).map(themeName => (
234            <ThemeButton
235              key={themeName}
236              active={currentTheme === themeName}
237              onClick={() => setCurrentTheme(themeName)}
238            >
239              {themeName.charAt(0).toUpperCase() + themeName.slice(1)}
240            </ThemeButton>
241          ))}
242        </ThemeSelector>
243
244        <CardsGrid>
245          {missions.map(mission => (
246            <Card key={mission.id}>
247              <CardTitle>{mission.name}</CardTitle>
248              <StatusBadge status={mission.status}>
249                {mission.status}
250              </StatusBadge>
251              <CardText>{mission.description}</CardText>
252              <Button variant="primary">Détails</Button>
253            </Card>
254          ))}
255        </CardsGrid>
256
257        <Footer>
258          <p>Styled-components permet d'écrire du CSS en JavaScript</p>
259          <p>avec une prise en charge complète des thèmes et des styles dynamiques</p>
260        </Footer>
261      </AppContainer>
262    </ThemeProvider>
263  );
264}
265
266export default App;

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quelle syntaxe SCSS est compatible avec le CSS ?

  2. 2. Que signifie l'opérateur & dans les sélecteurs SCSS imbriqués ?

Voici 2 des 5 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Tri horizontal

    Ordonne la commande d'installation de SCSS dans un projet React :

  • Clique dans l'ordre

    Ordonne la définition d'une variable SCSS :

  • Éditeur de code

    Système de variables SCSS

  • Éditeur de code

    Imbrication SCSS et l'opérateur &

  • Clique dans l'ordre

    Ordonne la définition d'un mixin en SCSS :

  • Tri vertical

    Ordonne les étapes pour ajouter SCSS à un projet React :

  • Tri vertical

    Ordonne les éléments de la directive @extend en SCSS, de gauche à droite :

  • Éditeur de code

    Mixins SCSS pour les composants

  • Tri vertical

    Ordonne un sélecteur SCSS imbriqué avec la pseudo-classe hover :

  • Éditeur de code

    Fonctions SCSS

  • Clique dans l'ordre

    Ordonne la syntaxe BEM en SCSS, du bloc au modificateur :

  • Tri vertical

    Classe les étapes de la compilation SCSS :

  • Éditeur de code

    CSS Modules avec SCSS

Articles utiles