Cours JavaScript et React · Module 7: Styles et animations
Construire ses propres design systems - L'architecture visuelle du futur cosmique
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 :
- Des principes de design - valeurs fondamentales et philosophie de conception
- Un système de composants - une bibliothèque d'éléments UI réutilisables
- Des design tokens - valeurs standardisées pour les couleurs, la typographie, les espacements, etc.
- Des patterns d'interaction - manières standard pour les utilisateurs d'interagir avec l'interface
- 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}>×</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)}>×</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 :
- Implémentation incrémentale - commence par de petits composants isolés
- Développement parallèle - construis d'abord le système dans une branche ou un projet séparé
- Migration page par page - migre une page ou une section à la fois
- 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 :
- Des design tokens - définis les variables de base pour les couleurs, la typographie et les espacements
- Des composants atomiques - crée au moins 3 composants de base (par exemple Button, Card, Input)
- Un composant complexe - utilise les composants atomiques pour créer un composant plus complexe (par exemple Dashboard Card, Mission Status)
- 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. Quelle est la fondation (la base) de tout design system ?
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.