Cours JavaScript et React · Module 7: Styles et animations

Construire ses propres design systems - L'architecture visuelle du futur cosmique

14 min de lecture
Dans cette leçon6

Lors de notre voyage intergalactique à travers le monde de React, nous arrivons à l'un des aspects les plus importants de la construction d'interfaces avancées : les design systems. Si les frameworks de composants sont comme des stations spatiales toutes prêtes, alors les design systems sont comme des plans pour bâtir des civilisations entières : cohérents, fonctionnels et évolutifs.

Qu'est-ce qu'un design system ?

Un design system est un ensemble de standards, de composants et de directives qui définissent comment les interfaces utilisateur sont créées à travers toute une application ou tout un écosystème de produits. C'est comme un code de construction pour les stations et véhicules spatiaux : il garantit que tous les éléments s'emboîtent, créant un ensemble cohérent, fonctionnel et esthétique.

Un bon design system comprend :

  1. Des principes de design - valeurs fondamentales et philosophie de conception
  2. Un système de composants - une bibliothèque d'éléments UI réutilisables
  3. Des design tokens - valeurs standardisées pour les couleurs, la typographie, les espacements, etc.
  4. Des patterns d'interaction - manières standard pour les utilisateurs d'interagir avec l'interface
  5. De la documentation - instructions et directives pour l'équipe

Pourquoi créer son propre design system ?

Créer ton propre design system peut sembler un grand défi, mais cela apporte de nombreux bénéfices :

  • Cohérence - tous les éléments de l'interface parlent le même langage visuel
  • Évolutivité - développement de l'application et ajout de nouvelles fonctionnalités facilités
  • Efficacité - conception et implémentation plus rapides grâce aux composants prêts
  • Meilleure collaboration - langage et standards communs pour les designers et les développeurs
  • Identité de marque - une expérience utilisateur unique et reconnaissable

Construire un design system à partir de zéro

Construire ton propre design system, c'est comme terraformer une nouvelle planète : cela demande de la vision, de la planification et une approche systématique. Passons en revue les étapes clés de ce processus.

1. Définir les principes de design

Commence par des principes fondamentaux qui guideront l'ensemble du système. Ce sont comme la constitution de ta civilisation cosmique. Exemples de principes :

  • Simplicité - Élimine la complexité chaque fois que c'est possible
  • Cohérence - Résous les problèmes similaires de manière similaire
  • Accessibilité - L'interface doit être utilisable par tout le monde
  • Performance - Optimise la vitesse et la réactivité
  • Inclusivité - Conçois en pensant à la diversité des besoins

2. Créer les design tokens

Les design tokens sont des variables qui stockent les valeurs stylistiques de base. Ce sont comme les éléments chimiques à partir desquels ton monde est construit.

Couleurs

1// design-tokens.js
2
3export const colors = {
4  // Palette principale
5  primary: {
6    50: '#e3f2fd',
7    100: '#bbdefb',
8    200: '#90caf9',
9    300: '#64b5f6',
10    400: '#42a5f5',
11    500: '#2196f3',  // Couleur principale
12    600: '#1e88e5',
13    700: '#1976d2',
14    800: '#1565c0',
15    900: '#0d47a1',
16  },
17
18  // Couleurs sémantiques
19  success: '#4caf50',
20  warning: '#ff9800',
21  error: '#f44336',
22  info: '#2196f3',
23
24  // Neutres
25  neutral: {
26    50: '#fafafa',
27    100: '#f5f5f5',
28    200: '#eeeeee',
29    300: '#e0e0e0',
30    400: '#bdbdbd',
31    500: '#9e9e9e',
32    600: '#757575',
33    700: '#616161',
34    800: '#424242',
35    900: '#212121',
36  },
37};

Typographie

1// design-tokens.js
2
3export const typography = {
4  fontFamily: {
5    base: '"Inter", system-ui, -apple-system, sans-serif',
6    heading: '"Space Grotesk", sans-serif',
7    monospace: '"Fira Code", monospace',
8  },
9
10  fontSize: {
11    xs: '0.75rem',     // 12px
12    sm: '0.875rem',    // 14px
13    base: '1rem',      // 16px
14    lg: '1.125rem',    // 18px
15    xl: '1.25rem',     // 20px
16    '2xl': '1.5rem',   // 24px
17    '3xl': '1.875rem', // 30px
18    '4xl': '2.25rem',  // 36px
19    '5xl': '3rem',     // 48px
20  },
21
22  fontWeight: {
23    light: 300,
24    regular: 400,
25    medium: 500,
26    semibold: 600,
27    bold: 700,
28  },
29
30  lineHeight: {
31    none: 1,
32    tight: 1.25,
33    snug: 1.375,
34    normal: 1.5,
35    relaxed: 1.625,
36    loose: 2,
37  },
38};

Espacements

1// design-tokens.js
2
3export const spacing = {
4  px: '1px',
5  0: '0',
6  0.5: '0.125rem',  // 2px
7  1: '0.25rem',     // 4px
8  1.5: '0.375rem',  // 6px
9  2: '0.5rem',      // 8px
10  2.5: '0.625rem',  // 10px
11  3: '0.75rem',     // 12px
12  3.5: '0.875rem',  // 14px
13  4: '1rem',        // 16px
14  5: '1.25rem',     // 20px
15  6: '1.5rem',      // 24px
16  8: '2rem',        // 32px
17  10: '2.5rem',     // 40px
18  12: '3rem',       // 48px
19  16: '4rem',       // 64px
20  20: '5rem',       // 80px
21  24: '6rem',       // 96px
22  32: '8rem',       // 128px
23  40: '10rem',      // 160px
24  48: '12rem',      // 192px
25  56: '14rem',      // 224px
26  64: '16rem',      // 256px
27};

Ombres

1// design-tokens.js
2
3export const shadows = {
4  sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
5  md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
6  lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
7  xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
8  '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
9  inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
10  outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
11  none: 'none',
12};

Breakpoints

1// design-tokens.js
2
3export const breakpoints = {
4  xs: '0px',
5  sm: '600px',
6  md: '960px',
7  lg: '1280px',
8  xl: '1920px',
9};

3. Construire des composants atomiques

Passons maintenant à la construction des composants atomiques de base qui formeront les blocs fondamentaux de ton système. C'est comme construire les premiers bâtiments sur une nouvelle planète.

Bouton

1import React from 'react';
2import styled, { css } from 'styled-components';
3import { colors, typography, spacing } from '../design-tokens';
4
5// Définition des variantes de bouton
6const variants = {
7  primary: css`
8    background-color: ${colors.primary[500]};
9    color: white;
10
11    &:hover {
12      background-color: ${colors.primary[600]};
13    }
14
15    &:focus {
16      box-shadow: 0 0 0 3px ${colors.primary[300]};
17    }
18  `,
19
20  secondary: css`
21    background-color: white;
22    color: ${colors.primary[500]};
23    border: 1px solid ${colors.primary[500]};
24
25    &:hover {
26      background-color: ${colors.primary[50]};
27    }
28
29    &:focus {
30      box-shadow: 0 0 0 3px ${colors.primary[300]};
31    }
32  `,
33
34  danger: css`
35    background-color: ${colors.error};
36    color: white;
37
38    &:hover {
39      background-color: #d32f2f;
40    }
41
42    &:focus {
43      box-shadow: 0 0 0 3px rgba(244, 67, 54, 0.4);
44    }
45  `,
46};
47
48// Définition des tailles de bouton
49const sizes = {
50  small: css`
51    padding: ${spacing[1]} ${spacing[2]};
52    font-size: ${typography.fontSize.sm};
53  `,
54
55  medium: css`
56    padding: ${spacing[2]} ${spacing[4]};
57    font-size: ${typography.fontSize.base};
58  `,
59
60  large: css`
61    padding: ${spacing[3]} ${spacing[6]};
62    font-size: ${typography.fontSize.lg};
63  `,
64};
65
66// Composant bouton
67const StyledButton = styled.button`
68  font-family: ${typography.fontFamily.base};
69  font-weight: ${typography.fontWeight.medium};
70  border-radius: 4px;
71  border: none;
72  cursor: pointer;
73  transition: all 0.2s ease-in-out;
74  display: inline-flex;
75  align-items: center;
76  justify-content: center;
77
78  &:disabled {
79    opacity: 0.6;
80    cursor: not-allowed;
81  }
82
83  /* Application de la variante */
84  ${props => variants[props.variant || 'primary']}
85
86  /* Application de la taille */
87  ${props => sizes[props.size || 'medium']}
88`;
89
90const Button = ({ children, variant, size, ...rest }) => (
91  <StyledButton variant={variant} size={size} {...rest}>
92    {children}
93  </StyledButton>
94);
95
96export default Button;

Carte

1import React from 'react';
2import styled from 'styled-components';
3import { spacing, shadows, colors } from '../design-tokens';
4
5const StyledCard = styled.div`
6  background-color: white;
7  border-radius: 8px;
8  box-shadow: ${shadows.md};
9  overflow: hidden;
10`;
11
12const CardHeader = styled.div`
13  padding: ${spacing[4]} ${spacing[4]} ${spacing[2]};
14  border-bottom: 1px solid ${colors.neutral[200]};
15`;
16
17const CardTitle = styled.h3`
18  margin: 0;
19  font-size: ${props => props.theme.typography.fontSize.xl};
20  font-weight: ${props => props.theme.typography.fontWeight.semibold};
21`;
22
23const CardSubtitle = styled.div`
24  color: ${colors.neutral[600]};
25  margin-top: ${spacing[1]};
26`;
27
28const CardContent = styled.div`
29  padding: ${spacing[4]};
30`;
31
32const CardFooter = styled.div`
33  padding: ${spacing[2]} ${spacing[4]};
34  background-color: ${colors.neutral[50]};
35  border-top: 1px solid ${colors.neutral[200]};
36  display: flex;
37  justify-content: flex-end;
38`;
39
40const Card = ({ children, ...rest }) => {
41  return <StyledCard {...rest}>{children}</StyledCard>;
42};
43
44Card.Header = CardHeader;
45Card.Title = CardTitle;
46Card.Subtitle = CardSubtitle;
47Card.Content = CardContent;
48Card.Footer = CardFooter;
49
50export default Card;

4. Construire des composants complexes

Une fois que tu disposes des composants de base, tu peux commencer à construire des éléments d'interface plus complexes, en utilisant les composants atomiques comme briques de construction. C'est comme créer des structures avancées après avoir posé les fondations.

Dialog (boîte de dialogue)

1import React, { useEffect, useRef } from 'react';
2import styled from 'styled-components';
3import Button from './Button';
4import { colors, spacing, shadows } from '../design-tokens';
5
6const Overlay = styled.div`
7  position: fixed;
8  top: 0;
9  left: 0;
10  right: 0;
11  bottom: 0;
12  background-color: rgba(0, 0, 0, 0.5);
13  display: flex;
14  align-items: center;
15  justify-content: center;
16  z-index: 1000;
17`;
18
19const DialogContainer = styled.div`
20  background-color: white;
21  border-radius: 8px;
22  box-shadow: ${shadows.xl};
23  width: 100%;
24  max-width: ${props =>
25    props.size === 'small' ? '400px' : props.size === 'large' ? '800px' : '600px'};
26  max-height: 90vh;
27  overflow-y: auto;
28  display: flex;
29  flex-direction: column;
30`;
31
32const DialogHeader = styled.div`
33  padding: ${spacing[4]};
34  border-bottom: 1px solid ${colors.neutral[200]};
35  display: flex;
36  align-items: center;
37  justify-content: space-between;
38`;
39
40const DialogTitle = styled.h3`
41  margin: 0;
42  font-weight: 600;
43`;
44
45const CloseButton = styled.button`
46  background: none;
47  border: none;
48  cursor: pointer;
49  color: ${colors.neutral[500]};
50  font-size: 1.5rem;
51  line-height: 1;
52
53  &:hover {
54    color: ${colors.neutral[700]};
55  }
56`;
57
58const DialogContent = styled.div`
59  padding: ${spacing[4]};
60  flex: 1;
61`;
62
63const DialogFooter = styled.div`
64  padding: ${spacing[3]} ${spacing[4]};
65  border-top: 1px solid ${colors.neutral[200]};
66  display: flex;
67  justify-content: flex-end;
68  gap: ${spacing[2]};
69`;
70
71const Dialog = ({
72  isOpen,
73  onClose,
74  title,
75  children,
76  size = 'medium',
77  confirmText = 'Confirm',
78  cancelText = 'Cancel',
79  onConfirm,
80  hideActions = false
81}) => {
82  const dialogRef = useRef(null);
83
84  // Ferme la boîte de dialogue à l'appui sur la touche Échap
85  useEffect(() => {
86    const handleKeyDown = (e) => {
87      if (e.key === 'Escape') {
88        onClose();
89      }
90    };
91
92    if (isOpen) {
93      window.addEventListener('keydown', handleKeyDown);
94    }
95
96    return () => {
97      window.removeEventListener('keydown', handleKeyDown);
98    };
99  }, [isOpen, onClose]);
100
101  // Empêche la propagation du clic à l'intérieur de la boîte de dialogue
102  const handleDialogClick = (e) => {
103    e.stopPropagation();
104  };
105
106  if (!isOpen) return null;
107
108  return (
109    <Overlay onClick={onClose}>
110      <DialogContainer size={size} onClick={handleDialogClick} ref={dialogRef}>
111        <DialogHeader>
112          <DialogTitle>{title}</DialogTitle>
113          <CloseButton onClick={onClose}>&times;</CloseButton>
114        </DialogHeader>
115        <DialogContent>{children}</DialogContent>
116        {!hideActions && (
117          <DialogFooter>
118            <Button variant="secondary" onClick={onClose}>
119              {cancelText}
120            </Button>
121            {onConfirm && (
122              <Button variant="primary" onClick={onConfirm}>
123                {confirmText}
124              </Button>
125            )}
126          </DialogFooter>
127        )}
128      </DialogContainer>
129    </Overlay>
130  );
131};
132
133export default Dialog;

5. Implémenter les patterns d'interaction

Les patterns d'interaction sont les manières standard dont les utilisateurs interagissent avec l'interface. Voici un exemple d'implémentation du pattern « Notification » (Toast) :

1import React, { useState, useEffect } from 'react';
2import styled, { css, keyframes } from 'styled-components';
3import { colors, spacing, shadows } from '../design-tokens';
4
5// Animation d'entrée
6const slideIn = keyframes`
7  from {
8    transform: translateX(100%);
9    opacity: 0;
10  }
11  to {
12    transform: translateX(0);
13    opacity: 1;
14  }
15`;
16
17// Animation de sortie
18const slideOut = keyframes`
19  from {
20    transform: translateX(0);
21    opacity: 1;
22  }
23  to {
24    transform: translateX(100%);
25    opacity: 0;
26  }
27`;
28
29// Différents types de notification
30const toastTypes = {
31  success: css`
32    background-color: ${colors.success};
33    color: white;
34  `,
35  error: css`
36    background-color: ${colors.error};
37    color: white;
38  `,
39  warning: css`
40    background-color: ${colors.warning};
41    color: white;
42  `,
43  info: css`
44    background-color: ${colors.info};
45    color: white;
46  `,
47};
48
49const ToastContainer = styled.div`
50  position: fixed;
51  top: ${spacing[4]};
52  right: ${spacing[4]};
53  z-index: 1500;
54  display: flex;
55  flex-direction: column;
56  gap: ${spacing[2]};
57`;
58
59const Toast = styled.div`
60  min-width: 300px;
61  max-width: 400px;
62  padding: ${spacing[3]};
63  border-radius: 4px;
64  box-shadow: ${shadows.lg};
65  display: flex;
66  align-items: center;
67  justify-content: space-between;
68  animation: ${props => (props.leaving ? slideOut : slideIn)} 0.3s ease-in-out forwards;
69
70  ${props => toastTypes[props.type || 'info']}
71`;
72
73const CloseButton = styled.button`
74  background: none;
75  border: none;
76  color: inherit;
77  opacity: 0.7;
78  cursor: pointer;
79  font-size: 1.25rem;
80
81  &:hover {
82    opacity: 1;
83  }
84`;
85
86// Contexte pour les notifications
87const ToastContext = React.createContext();
88
89// Fournisseur de notifications
90export const ToastProvider = ({ children }) => {
91  const [toasts, setToasts] = useState([]);
92
93  const addToast = (message, type = 'info', duration = 5000) => {
94    const id = Date.now();
95    setToasts(prev => [...prev, { id, message, type, duration }]);
96
97    if (duration !== Infinity) {
98      setTimeout(() => removeToast(id), duration);
99    }
100
101    return id;
102  };
103
104  const removeToast = (id) => {
105    setToasts(prev => {
106      const toast = prev.find(t => t.id === id);
107      if (toast) {
108        // Marque le toast comme en cours de sortie
109        return prev.map(t => t.id === id ? { ...t, leaving: true } : t);
110      }
111      return prev;
112    });
113
114    // Supprime le toast après la fin de l'animation
115    setTimeout(() => {
116      setToasts(prev => prev.filter(t => t.id !== id));
117    }, 300); // Durée de l'animation
118  };
119
120  return (
121    <ToastContext.Provider value={{ addToast, removeToast }}>
122      {children}
123      <ToastContainer>
124        {toasts.map(toast => (
125          <Toast key={toast.id} type={toast.type} leaving={toast.leaving}>
126            <span>{toast.message}</span>
127            <CloseButton onClick={() => removeToast(toast.id)}>&times;</CloseButton>
128          </Toast>
129        ))}
130      </ToastContainer>
131    </ToastContext.Provider>
132  );
133};
134
135// Hook pour utiliser les notifications
136export const useToast = () => {
137  const context = React.useContext(ToastContext);
138  if (!context) {
139    throw new Error('useToast must be used within a ToastProvider');
140  }
141  return context;
142};
143
144// Exemple d'utilisation :
145// const { addToast } = useToast();
146// addToast('Opération réalisée avec succès !', 'success');

6. Documentation du design system

La dernière étape, mais non la moins importante, est de créer une documentation qui permet aux autres développeurs et designers d'utiliser ton design system. Parmi les outils populaires de documentation, on trouve :

  • Storybook - documentation interactive des composants
  • Docusaurus - générateur de sites de documentation
  • Docz - un outil dédié à la documentation des composants React

Exemple de configuration d'un composant dans Storybook :

1// Button.stories.js
2
3import React from 'react';
4import Button from './Button';
5
6export default {
7  title: 'Atoms/Button',
8  component: Button,
9  argTypes: {
10    variant: {
11      control: { type: 'select', options: ['primary', 'secondary', 'danger'] },
12      description: 'Variante du bouton qui détermine son style et son usage',
13      table: {
14        type: { summary: 'string' },
15        defaultValue: { summary: 'primary' },
16      },
17    },
18    size: {
19      control: { type: 'select', options: ['small', 'medium', 'large'] },
20      description: 'Taille du bouton',
21      table: {
22        type: { summary: 'string' },
23        defaultValue: { summary: 'medium' },
24      },
25    },
26    disabled: {
27      control: 'boolean',
28      description: 'Indique si le bouton est désactivé',
29      table: {
30        type: { summary: 'boolean' },
31        defaultValue: { summary: false },
32      },
33    },
34  },
35};
36
37// Utilisation de base
38export const Primary = () => <Button variant="primary">Bouton principal</Button>;
39
40// Variantes
41export const Variants = () => (
42  <div style={{ display: 'flex', gap: '8px' }}>
43    <Button variant="primary">Primary</Button>
44    <Button variant="secondary">Secondary</Button>
45    <Button variant="danger">Danger</Button>
46  </div>
47);
48
49// Tailles
50export const Sizes = () => (
51  <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
52    <Button size="small">Petit</Button>
53    <Button size="medium">Moyen</Button>
54    <Button size="large">Grand</Button>
55  </div>
56);
57
58// État désactivé
59export const Disabled = () => <Button disabled>Bouton désactivé</Button>;

Stratégies d'implémentation d'un design system

Implémenter un design system dans un projet existant, c'est comme moderniser une station spatiale en fonctionnement : cela demande de la planification et une approche progressive :

  1. Implémentation incrémentale - commence par de petits composants isolés
  2. Développement parallèle - construis d'abord le système dans une branche ou un projet séparé
  3. Migration page par page - migre une page ou une section à la fois
  4. Composant par composant - remplace les anciens composants par les nouveaux un à un

Bonnes pratiques pour créer des design systems

  • Commence par de la recherche - analyse les concurrents et les patterns éprouvés
  • Collabore dès le début - implique les designers et les développeurs
  • Pense de manière systémique - conçois un système, pas des écrans isolés
  • Teste tôt et souvent - recueille les retours des utilisateurs
  • Documente tout - pas seulement le code, mais aussi les décisions et leurs justifications
  • Fais évoluer, ne révolutionne pas - un design system doit être une entité vivante et évolutive

Exercice pratique : Crée un micro design system pour une application spatiale

Conçois et implémente un petit design system pour une application de surveillance de mission cosmique. Ton système doit contenir :

  1. Des design tokens - définis les variables de base pour les couleurs, la typographie et les espacements
  2. Des composants atomiques - crée au moins 3 composants de base (par exemple Button, Card, Input)
  3. Un composant complexe - utilise les composants atomiques pour créer un composant plus complexe (par exemple Dashboard Card, Mission Status)
  4. De la documentation - décris ton système et tes composants

N'oublie pas qu'un bon design system n'est pas qu'une question d'esthétique, mais avant tout de fonctionnalité, de cohérence et de facilité d'utilisation. Crée le système en pensant à l'évolution future de l'application.

Quand tu crées ton propre design system, tu deviens non seulement programmeur mais aussi architecte du futur cosmique des interfaces. Comme les créateurs des premières colonies sur d'autres planètes, tu bâtis les fondations sur lesquelles les futures générations de développeurs et d'utilisateurs construiront.

Code de cette leçon : App.jsx
1import React, { useState } from 'react';
2import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
3
4// ===== DESIGN TOKENS =====
5// Les design tokens sont des variables qui stockent les valeurs stylistiques de base
6const tokens = {
7  colors: {
8    primary: '#6c5ce7',
9    primaryLight: '#a29bfe',
10    secondary: '#00cec9',
11    success: '#00b894',
12    warning: '#fdcb6e',
13    danger: '#d63031',
14    text: '#2d3436',
15    textLight: '#636e72',
16    background: '#f8f9fa',
17    surface: '#ffffff',
18    border: '#dfe6e9',
19  },
20  spacing: {
21    xs: '4px',
22    sm: '8px',
23    md: '16px',
24    lg: '24px',
25    xl: '32px',
26    xxl: '48px',
27  },
28  radius: {
29    sm: '4px',
30    md: '8px',
31    lg: '16px',
32    full: '9999px',
33  },
34  fontSize: {
35    xs: '0.75rem',
36    sm: '0.875rem',
37    base: '1rem',
38    lg: '1.125rem',
39    xl: '1.5rem',
40    xxl: '2rem',
41  },
42  fontWeight: {
43    normal: 400,
44    medium: 500,
45    semibold: 600,
46    bold: 700,
47  },
48  shadows: {
49    sm: '0 1px 3px rgba(0,0,0,0.12)',
50    md: '0 4px 6px rgba(0,0,0,0.1)',
51    lg: '0 10px 25px rgba(0,0,0,0.15)',
52  },
53};
54
55// ===== GLOBAL STYLES =====
56const GlobalStyle = createGlobalStyle`
57  * { margin: 0; padding: 0; box-sizing: border-box; }
58  body {
59    font-family: 'Segoe UI', system-ui, sans-serif;
60    background: ${tokens.colors.background};
61    color: ${tokens.colors.text};
62  }
63`;
64
65// ===== COMPOSANT : BUTTON =====
66// Bouton avec variantes : primary, secondary, danger
67const Button = styled.button`
68  display: inline-flex;
69  align-items: center;
70  justify-content: center;
71  padding: ${tokens.spacing.sm} ${tokens.spacing.lg};
72  font-size: ${tokens.fontSize.sm};
73  font-weight: ${tokens.fontWeight.semibold};
74  border-radius: ${tokens.radius.md};
75  border: 2px solid transparent;
76  cursor: pointer;
77  transition: all 0.2s ease;
78
79  background: ${p => {
80    if (p.variant === 'secondary') return 'transparent';
81    if (p.variant === 'danger') return tokens.colors.danger;
82    return tokens.colors.primary;
83  }};
84
85  color: ${p => {
86    if (p.variant === 'secondary') return tokens.colors.primary;
87    return '#ffffff';
88  }};
89
90  border-color: ${p => {
91    if (p.variant === 'secondary') return tokens.colors.primary;
92    return 'transparent';
93  }};
94
95  &:hover {
96    transform: translateY(-2px);
97    box-shadow: ${tokens.shadows.md};
98    opacity: 0.9;
99  }
100
101  &:disabled {
102    opacity: 0.5;
103    cursor: not-allowed;
104    transform: none;
105  }
106`;
107
108// ===== COMPOSANT : CARD =====
109const Card = styled.div`
110  background: ${tokens.colors.surface};
111  border-radius: ${tokens.radius.lg};
112  padding: ${tokens.spacing.lg};
113  box-shadow: ${tokens.shadows.sm};
114  border: 1px solid ${tokens.colors.border};
115  transition: all 0.3s ease;
116
117  &:hover {
118    box-shadow: ${tokens.shadows.lg};
119    transform: translateY(-4px);
120  }
121`;
122
123// ===== COMPOSANT : INPUT =====
124const Input = styled.input`
125  width: 100%;
126  padding: ${tokens.spacing.sm} ${tokens.spacing.md};
127  font-size: ${tokens.fontSize.base};
128  border: 2px solid ${tokens.colors.border};
129  border-radius: ${tokens.radius.md};
130  background: ${tokens.colors.surface};
131  color: ${tokens.colors.text};
132  transition: border-color 0.2s ease;
133  outline: none;
134
135  &:focus {
136    border-color: ${tokens.colors.primary};
137    box-shadow: 0 0 0 3px ${tokens.colors.primaryLight}40;
138  }
139
140  &::placeholder {
141    color: ${tokens.colors.textLight};
142  }
143`;
144
145// TODO : Crée un composant Badge avec variantes
146// Le Badge doit afficher de petites étiquettes de statut
147// Variantes : success, warning, danger
148// Utilise les tokens de l'objet tokens
149const Badge = styled.span`
150  display: inline-block;
151  padding: 2px ${tokens.spacing.sm};
152  font-size: ${tokens.fontSize.xs};
153  font-weight: ${tokens.fontWeight.bold};
154  border-radius: ${tokens.radius.full};
155  /* TODO : Ajoute une couleur de fond dynamique selon props.variant */
156  /* Utilise tokens.colors[props.variant] avec opacity 20% */
157  /* par exemple success -> fond vert, warning -> fond jaune */
158  background: ${p => (tokens.colors[p.variant] || tokens.colors.primary) + '33'};
159  color: ${p => tokens.colors[p.variant] || tokens.colors.primary};
160`;
161
162// TODO : Crée un composant Alert pour afficher des messages
163// L'Alert doit avoir des variantes : success, warning, danger
164// Utilise les tokens spacing, colors et radius
165const Alert = styled.div`
166  padding: ${tokens.spacing.md};
167  border-radius: ${tokens.radius.md};
168  margin-bottom: ${tokens.spacing.md};
169  border-left: 4px solid ${p => tokens.colors[p.variant] || tokens.colors.primary};
170  /* TODO : Ajoute un fond avec une légère transparence de la couleur de la variante */
171  background: ${p => (tokens.colors[p.variant] || tokens.colors.primary) + '15'};
172  color: ${tokens.colors.text};
173`;
174
175// ===== LAYOUT =====
176const Container = styled.div`
177  max-width: 700px;
178  margin: 0 auto;
179  padding: ${tokens.spacing.xl};
180`;
181
182const Title = styled.h1`
183  font-size: ${tokens.fontSize.xxl};
184  font-weight: ${tokens.fontWeight.bold};
185  color: ${tokens.colors.primary};
186  text-align: center;
187  margin-bottom: ${tokens.spacing.xs};
188`;
189
190const Subtitle = styled.p`
191  text-align: center;
192  color: ${tokens.colors.textLight};
193  margin-bottom: ${tokens.spacing.xl};
194`;
195
196const Section = styled.div`
197  margin-bottom: ${tokens.spacing.xl};
198`;
199
200const SectionTitle = styled.h2`
201  font-size: ${tokens.fontSize.lg};
202  margin-bottom: ${tokens.spacing.md};
203  color: ${tokens.colors.text};
204`;
205
206const ButtonGroup = styled.div`
207  display: flex;
208  gap: ${tokens.spacing.sm};
209  flex-wrap: wrap;
210`;
211
212const Grid = styled.div`
213  display: grid;
214  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
215  gap: ${tokens.spacing.md};
216`;
217
218// ===== APPLICATION =====
219function DesignSystemDemo() {
220  const [inputValue, setInputValue] = useState('');
221
222  return (
223    <>
224      <GlobalStyle />
225      <Container>
226        <Title>Design system cosmique</Title>
227        <Subtitle>Un ensemble cohérent de composants UI</Subtitle>
228
229        {/* Section : Alertes */}
230        <Section>
231          <SectionTitle>Alertes</SectionTitle>
232          <Alert variant="success">Mission accomplie avec succès !</Alert>
233          <Alert variant="warning">Niveau de carburant bas</Alert>
234          <Alert variant="danger">Anomalie gravitationnelle détectée</Alert>
235        </Section>
236
237        {/* Section : Boutons */}
238        <Section>
239          <SectionTitle>Boutons</SectionTitle>
240          <ButtonGroup>
241            <Button>Primary</Button>
242            <Button variant="secondary">Secondary</Button>
243            <Button variant="danger">Danger</Button>
244            <Button disabled>Disabled</Button>
245          </ButtonGroup>
246        </Section>
247
248        {/* Section : Badge */}
249        <Section>
250          <SectionTitle>Badge</SectionTitle>
251          <ButtonGroup>
252            <Badge variant="success">Online</Badge>
253            <Badge variant="warning">Standby</Badge>
254            <Badge variant="danger">Offline</Badge>
255            <Badge>Default</Badge>
256          </ButtonGroup>
257        </Section>
258
259        {/* Section : Input */}
260        <Section>
261          <SectionTitle>Input</SectionTitle>
262          <Input
263            placeholder="Saisis les coordonnées de la cible..."
264            value={inputValue}
265            onChange={e => setInputValue(e.target.value)}
266          />
267        </Section>
268
269        {/* Section : Cartes */}
270        <Section>
271          <SectionTitle>Cartes</SectionTitle>
272          <Grid>
273            <Card>
274              <h3 style={{ marginBottom: 8 }}>Navigation</h3>
275              <p style={{ color: tokens.colors.textLight, fontSize: 14, marginBottom: 12 }}>
276                Système de navigation cosmique
277              </p>
278              <Badge variant="success">Active</Badge>
279            </Card>
280            <Card>
281              <h3 style={{ marginBottom: 8 }}>Moteurs</h3>
282              <p style={{ color: tokens.colors.textLight, fontSize: 14, marginBottom: 12 }}>
283                Propulsion warp
284              </p>
285              <Badge variant="warning">Standby</Badge>
286            </Card>
287            <Card>
288              <h3 style={{ marginBottom: 8 }}>Boucliers</h3>
289              <p style={{ color: tokens.colors.textLight, fontSize: 14, marginBottom: 12 }}>
290                Boucliers électromagnétiques
291              </p>
292              <Badge variant="danger">Offline</Badge>
293            </Card>
294          </Grid>
295        </Section>
296      </Container>
297    </>
298  );
299}
300
301export default DesignSystemDemo;

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 est la fondation (la base) de tout design system ?

  2. 2. Quel outil du navigateur permet de voir les styles finaux (computed) appliqués à un élément HTML ?

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

Articles utiles