- Documentation archivée et réorganisée - Backend: Ajout tests, migrations, library service, rate limiting - Frontend: Suppression Flutter, focus sur interface web HTML/JS - Tailwind CSS ajouté pour le style - Améliorations UX et corrections bugs Generated with [Claude Code](https://claude.com/claude-code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
20 KiB
Guide d'Implémentation du Design System - AudiOhm
Ce guide vous explique comment appliquer le nouveau système de design moderne à votre application Flutter AudiOhm.
📋 Sommaire
- Vue d'ensemble
- Structure du design system
- Implémentation dans Flutter
- Migration du code existant
- Checklist de validation
Vue d'ensemble
Objectifs
✅ Moderniser l'UI/UX selon les standards 2025 ✅ Préserver l'identité cyberpunk néon ✅ Améliorer l'accessibilité (WCAG AA) ✅ Optimiser les performances ✅ Standardiser les composants
Changements Majeurs
| Aspect | Avant | Après |
|---|---|---|
| Contraste | Parfois faible | Minimum 4.5:1 (WCAG AA) |
| Icônes | Mixtes | SVG unifiés (Lucide) |
| Transitions | Instables ou 0ms | 150-300ms standardisées |
| Spacing | Incohérent | Système de 4px |
| Typography | Outfit uniquement | Space Grotesk + Outfit |
| Couleurs | Néon sans structure | Palette sémantique claire |
Structure du Design System
Fichiers Créés
design-system/
├── MASTER.md # Règles globales (source de vérité)
└── pages/
├── home.md # Override pour page d'accueil
├── search.md # Override pour page de recherche
└── player.md # Override pour page lecteur
Comment Utiliser
Pour chaque page/component que vous créez ou modifiez :
- Consultez d'abord le MASTER.md pour les règles de base
- Vérifiez s'il existe un override pour la page spécifique
- Si un override existe, ses règles priment sur le MASTER
- Sinon, appliquez les règles du MASTER
Exemple :
"Je crée la page Player"
→ Lire design-system/MASTER.md
→ Lire design-system/pages/player.md
→ Les règles de player.md priment sur MASTER.md
Implémentation dans Flutter
1. Créer le fichier de couleurs
Créez lib/core/theme/colors.dart:
import 'package:flutter/material.dart';
class AppColors {
// Background Colors
static const Color background = Color(0xFF0A0E27);
static const Color surface = Color(0xFF151932);
static const Color surfaceElevated = Color(0xFF1F2342);
static const Color border = Color(0xFF2A2F4A);
// Neon Accents
static const Color primary = Color(0xFF00F0FF);
static const Color secondary = Color(0xFFBF00FF);
static const Color accent = Color(0xFFFF006E);
static const Color success = Color(0xFF00FF94);
static const Color warning = Color(0xFFFFB800);
static const Color error = Color(0xFFFF3B3B);
// Text Colors
static const Color textPrimary = Color(0xFFF0F4F8);
static const Color textSecondary = Color(0xFF9BA3B8);
static const Color textTertiary = Color(0xFF6B7280);
static const Color textInverted = Color(0xFF0A0E27);
// Gradients
static const LinearGradient primaryGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [primary, Color(0xFF00C8FF)],
);
static const LinearGradient secondaryGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [accent, error],
);
}
2. Créer le fichier de typography
Créez lib/core/theme/typography.dart:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class AppTypography {
// Heading Font - Space Grotesk
static TextStyle get heading {
return GoogleFonts.spaceGrotesk(
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
);
}
// Body Font - Outfit
static TextStyle get body {
return GoogleFonts.outfit(
fontWeight: FontWeight.w400,
color: AppColors.textPrimary,
);
}
// Type Scale
static const double displaySize = 48.0;
static const double h1Size = 36.0;
static const double h2Size = 28.0;
static const double h3Size = 22.0;
static const double bodyLargeSize = 18.0;
static const double bodySize = 16.0;
static const double bodySmallSize = 14.0;
static const double captionSize = 12.0;
static const double overlineSize = 11.0;
// Text Styles
static TextStyle get display => heading.copyWith(fontSize: displaySize);
static TextStyle get h1 => heading.copyWith(fontSize: h1Size);
static TextStyle get h2 => heading.copyWith(
fontSize: h2Size,
fontWeight: FontWeight.w600,
);
static TextStyle get h3 => heading.copyWith(
fontSize: h3Size,
fontWeight: FontWeight.w600,
);
static TextStyle get bodyLarge => body.copyWith(
fontSize: bodyLargeSize,
height: 1.5,
);
static TextStyle get bodyText => body.copyWith(
fontSize: bodySize,
height: 1.6,
);
static TextStyle get bodySmall => body.copyWith(
fontSize: bodySmallSize,
height: 1.6,
color: AppColors.textSecondary,
);
static TextStyle get caption => body.copyWith(
fontSize: captionSize,
fontWeight: FontWeight.w500,
height: 1.5,
color: AppColors.textSecondary,
);
static TextStyle get overline => body.copyWith(
fontSize: overlineSize,
fontWeight: FontWeight.w600,
height: 1.4,
color: AppColors.textPrimary,
letterSpacing: 0.5,
);
}
3. Créer le thème MaterialApp
Créez lib/core/theme/app_theme.dart:
import 'package:flutter/material.dart';
import 'colors.dart';
import 'typography.dart';
class AppTheme {
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
// Color Scheme
colorScheme: const ColorScheme.dark(
primary: AppColors.primary,
secondary: AppColors.secondary,
surface: AppColors.surface,
error: AppColors.error,
onPrimary: AppColors.textInverted,
onSecondary: AppColors.textPrimary,
onSurface: AppColors.textPrimary,
onError: AppColors.textPrimary,
),
// Scaffold
scaffoldBackgroundColor: AppColors.background,
// Typography
fontFamily: 'Outfit',
textTheme: TextTheme(
displayLarge: AppTypography.display,
headlineMedium: AppTypography.h1,
headlineSmall: AppTypography.h2,
titleLarge: AppTypography.h3,
bodyLarge: AppTypography.bodyLarge,
bodyMedium: AppTypography.bodyText,
bodySmall: AppTypography.bodySmall,
labelSmall: AppTypography.caption,
),
// Card Theme
cardTheme: CardTheme(
color: AppColors.surface,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
side: BorderSide(color: AppColors.border, width: 1),
),
// Input Decoration
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.background,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: AppColors.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: AppColors.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: AppColors.primary),
),
focusColor: AppColors.primary,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
// Elevated Button Theme
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: AppColors.textInverted,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
textStyle: AppTypography.bodyText.copyWith(
fontWeight: FontWeight.w600,
),
).copyWith(
elevation: MaterialStateProperty.resolveWith<double>((states) {
if (states.contains(MaterialState.pressed)) return 0;
if (states.contains(MaterialState.hovered)) return 4;
return 0;
}),
),
),
// Outline Button Theme
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primary,
side: BorderSide(color: AppColors.primary),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
textStyle: AppTypography.bodyText.copyWith(
fontWeight: FontWeight.w600,
),
),
),
// Icon Theme
iconTheme: const IconThemeData(
color: AppColors.textSecondary,
size: 24,
),
// Divider
dividerTheme: const DividerThemeData(
color: AppColors.border,
thickness: 1,
space: 1,
),
);
}
}
4. Créer des composants réutilisables
Bouton Primaire avec Glow
Créez lib/widgets/buttons/primary_button.dart:
import 'package:flutter/material.dart';
import '../core/theme/colors.dart';
class PrimaryButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final bool isLoading;
final double? width;
const PrimaryButton({
Key? key,
required this.text,
this.onPressed,
this.isLoading = false,
this.width,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: width,
decoration: BoxDecoration(
gradient: AppColors.primaryGradient,
borderRadius: BorderRadius.circular(8),
boxShadow: onPressed != null
? [
BoxShadow(
color: AppColors.primary.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 4),
),
]
: null,
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: isLoading ? null : onPressed,
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
child: Center(
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(AppColors.textInverted),
),
)
: Text(
text,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: AppColors.textInverted,
),
),
),
),
),
),
);
}
}
Carte Album avec Hover
Créez lib/widgets/cards/album_card.dart:
import 'package:flutter/material.dart';
import '../../core/theme/colors.dart';
import '../../core/theme/typography.dart';
class AlbumCard extends StatefulWidget {
final String imageUrl;
final String title;
final String subtitle;
final VoidCallback? onTap;
const AlbumCard({
Key? key,
required this.imageUrl,
required this.title,
required this.subtitle,
this.onTap,
}) : super(key: key);
@override
State<AlbumCard> createState() => _AlbumCardState();
}
class _AlbumCardState extends State<AlbumCard>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
bool _isHovered = false;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.02).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) {
setState(() => _isHovered = true);
_animationController.forward();
},
onExit: (_) {
setState(() => _isHovered = false);
_animationController.reverse();
},
child: ScaleTransition(
scale: _scaleAnimation,
child: GestureDetector(
onTap: widget.onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Album Art
Stack(
children: [
Container(
width: double.infinity,
aspectRatio: 1,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 4),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
widget.imageUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
color: AppColors.surface,
child: Icon(
Icons.music_note,
size: 48,
color: AppColors.textTertiary,
),
);
},
),
),
),
// Play Overlay
if (_isHovered)
Positioned.fill(
child: Container(
decoration: BoxDecoration(
color: AppColors.background.withOpacity(0.7),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: AppColors.primary,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColors.primary.withOpacity(0.4),
blurRadius: 24,
offset: const Offset(0, 4),
),
],
),
child: Icon(
Icons.play_arrow,
color: AppColors.textInverted,
size: 32,
),
),
),
),
),
],
),
const SizedBox(height: 12),
// Title
Text(
widget.title,
style: AppTypography.h3.copyWith(
fontSize: 16,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
),
const SizedBox(height: 4),
// Subtitle
Text(
widget.subtitle,
style: AppTypography.bodySmall,
maxLines: 1,
),
],
),
),
),
),
}
}
Migration du Code Existant
Priorités de Migration
-
Phase 1 - Fondation (Priorité Haute)
- ✅ Implémenter
colors.dart - ✅ Implémenter
typography.dart - ✅ Implémenter
app_theme.dart - ✅ Mettre à jour
main.dartavec le nouveau thème
- ✅ Implémenter
-
Phase 2 - Composants (Priorité Haute)
- ✅ Créer
PrimaryButton - ✅ Créer
AlbumCard - ✅ Créer
SearchInput - ✅ Créer
ProgressBar(player)
- ✅ Créer
-
Phase 3 - Pages (Priorité Moyenne)
- ✅ Migrer la page Home
- ✅ Migrer la page Search
- ✅ Migrer la page Player
-
Phase 4 - Finitions (Priorité Basse)
- Animations et transitions
- États de loading
- États empty
Checklist par Page
Page Home
- Hero section avec gradient animé
- Quick picks grid
- Horizontal scroll rows
- Skeleton loading states
- Category pills
Page Search
- Search bar avec clear button
- Search tabs
- Recent searches
- Trending searches
- Results grid/list
Page Player
- Large album art avec glow
- Progress bar avec handle
- Control buttons (primary + secondary)
- Volume slider
- Queue panel
- Mini player sticky
Checklist de Validation
Avant de considérer une page comme terminée, vérifiez :
Visuel
- Pas d'emojis comme icônes (SVG seulement)
- Icônes cohérentes (Lucide/Heroicons)
- Hover states sans layout shift
- Couleurs du thème utilisées directement
- Effets néon subtils, pas écrasants
Interaction
- Tous les éléments cliquables ont
cursor: pointer - Hover states fournissent feedback clair
- Transitions 150-300ms
- Focus states visibles
Accessibilité
- Contraste texte minimum 4.5:1
- Toutes les images ont alt text
- Inputs ont labels
- Tabulation fonctionne
prefers-reduced-motionrespecté
Responsive
- Fonctionne à 375px (mobile)
- Fonctionne à 768px (tablet)
- Fonctionne à 1024px (desktop)
- Pas de scroll horizontal mobile
- Touch targets min 44x44px
Performance
- Images WebP avec fallbacks
- Lazy loading pour images larges
- Animations utilisent transform/opacity
- Pas de layout shifts
Ressources Utiles
Fonts Google
- Space Grotesk: https://fonts.google.com/specimen/Space+Grotesk
- Outfit: https://fonts.google.com/specimen/Outfit
- JetBrains Mono: https://fonts.google.com/specimen/JetBrains+Mono
Icônes
- Lucide Icons: https://lucide.dev/
- Heroicons: https://heroicons.com/
Outils de Contraste
- WebAIM Contrast Checker: https://webaim.org/resources/contrastchecker/
Documentation Flutter
- Theme Data: https://api.flutter.dev/flutter/material/ThemeData-class.html
- Animation Controller: https://api.flutter.dev/flutter/animation/AnimationController-class.html
Prochaines Étapes
- ✅ Design system créé - MASTER.md + overrides
- 🔄 Implémenter les fichiers de thème - colors, typography, app_theme
- 🔄 Créer les composants de base - buttons, cards, inputs
- 🔄 Migrer page par page - Commencer par Home
- 🔄 Tester et valider - Accessibilité, responsive, performance
Besoin d'aide? Référez-vous toujours aux fichiers dans design-system/ pour les règles spécifiques à chaque page.