Files
AudiOhm/archives/docs/DESIGN_IMPLEMENTATION_GUIDE.md
T
root 801e6a050b prod: UI Optimisée mise en production
- 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>
2026-01-20 09:56:39 +00:00

691 lines
20 KiB
Markdown

# 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
1. [Vue d'ensemble](#vue-densemble)
2. [Structure du design system](#structure-du-design-system)
3. [Implémentation dans Flutter](#implémentation-dans-flutter)
4. [Migration du code existant](#migration-du-code-existant)
5. [Checklist de validation](#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 :
1. **Consultez d'abord le MASTER.md** pour les règles de base
2. **Vérifiez s'il existe un override** pour la page spécifique
3. **Si un override existe**, ses règles priment sur le MASTER
4. **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`:
```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`:
```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`:
```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`:
```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`:
```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
1. **Phase 1** - Fondation (Priorité Haute)
- ✅ Implémenter `colors.dart`
- ✅ Implémenter `typography.dart`
- ✅ Implémenter `app_theme.dart`
- ✅ Mettre à jour `main.dart` avec le nouveau thème
2. **Phase 2** - Composants (Priorité Haute)
- ✅ Créer `PrimaryButton`
- ✅ Créer `AlbumCard`
- ✅ Créer `SearchInput`
- ✅ Créer `ProgressBar` (player)
3. **Phase 3** - Pages (Priorité Moyenne)
- ✅ Migrer la page Home
- ✅ Migrer la page Search
- ✅ Migrer la page Player
4. **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-motion` respecté
### 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
1.**Design system créé** - MASTER.md + overrides
2. 🔄 **Implémenter les fichiers de thème** - colors, typography, app_theme
3. 🔄 **Créer les composants de base** - buttons, cards, inputs
4. 🔄 **Migrer page par page** - Commencer par Home
5. 🔄 **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.