gitignore propre
This commit is contained in:
157
unionflow-mobile-apps/DESIGN_SYSTEM_GUIDE.md
Normal file
157
unionflow-mobile-apps/DESIGN_SYSTEM_GUIDE.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Guide d'Utilisation - UnionFlow Design System
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Date**: 2025-10-05
|
||||
**Palette**: Bleu Roi (#4169E1) + Bleu Pétrole (#2C5F6F)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Table des Matières
|
||||
|
||||
1. [Introduction](#introduction)
|
||||
2. [Installation](#installation)
|
||||
3. [Tokens](#tokens)
|
||||
4. [Composants](#composants)
|
||||
5. [Exemples](#exemples)
|
||||
6. [Règles d'Utilisation](#règles-dutilisation)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Introduction
|
||||
|
||||
Le Design System UnionFlow est un système de design unifié basé sur Material Design 3 et les tendances UI/UX 2024-2025. Il fournit une palette de couleurs cohérente, des tokens de design et des composants réutilisables.
|
||||
|
||||
### Palette de Couleurs
|
||||
|
||||
**Mode Jour**
|
||||
- Primary: `#4169E1` (Bleu Roi)
|
||||
- Secondary: `#6366F1` (Indigo)
|
||||
- Tertiary: `#10B981` (Vert Émeraude)
|
||||
|
||||
**Mode Nuit**
|
||||
- Primary: `#2C5F6F` (Bleu Pétrole)
|
||||
- Secondary: `#4F46E5` (Indigo Sombre)
|
||||
- Tertiary: `#059669` (Vert Sombre)
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### Import Unique
|
||||
|
||||
Importez le Design System dans vos fichiers :
|
||||
|
||||
```dart
|
||||
import 'package:unionflow_mobile_apps/core/design_system/unionflow_design_system.dart';
|
||||
```
|
||||
|
||||
Cet import donne accès à :
|
||||
- `ColorTokens` - Couleurs
|
||||
- `TypographyTokens` - Typographie
|
||||
- `SpacingTokens` - Espacements
|
||||
- `UFPrimaryButton`, `UFSecondaryButton` - Boutons
|
||||
- `UFStatCard` - Cards de statistiques
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Tokens
|
||||
|
||||
### Couleurs (ColorTokens)
|
||||
|
||||
#### Couleurs Primaires
|
||||
|
||||
```dart
|
||||
// Mode Jour
|
||||
ColorTokens.primary // #4169E1 - Bleu Roi
|
||||
ColorTokens.primaryLight // #6B8EF5 - Bleu Roi Clair
|
||||
ColorTokens.primaryDark // #2952C8 - Bleu Roi Sombre
|
||||
ColorTokens.onPrimary // #FFFFFF - Texte sur primaire
|
||||
|
||||
// Mode Nuit
|
||||
ColorTokens.primaryDarkMode // #2C5F6F - Bleu Pétrole
|
||||
ColorTokens.onPrimaryDarkMode // #E5E7EB - Texte sur primaire
|
||||
```
|
||||
|
||||
#### Couleurs Sémantiques
|
||||
|
||||
```dart
|
||||
ColorTokens.success // #10B981 - Vert Succès
|
||||
ColorTokens.error // #DC2626 - Rouge Erreur
|
||||
ColorTokens.warning // #F59E0B - Orange Avertissement
|
||||
ColorTokens.info // #0EA5E9 - Bleu Info
|
||||
```
|
||||
|
||||
### Typographie (TypographyTokens)
|
||||
|
||||
```dart
|
||||
TypographyTokens.headlineLarge // 32px - Titres de section
|
||||
TypographyTokens.headlineMedium // 28px
|
||||
TypographyTokens.bodyLarge // 16px - Corps de texte
|
||||
TypographyTokens.buttonLarge // 16px - Boutons
|
||||
```
|
||||
|
||||
### Espacements (SpacingTokens)
|
||||
|
||||
```dart
|
||||
SpacingTokens.xs // 2px
|
||||
SpacingTokens.sm // 4px
|
||||
SpacingTokens.md // 8px
|
||||
SpacingTokens.lg // 12px
|
||||
SpacingTokens.xl // 16px
|
||||
SpacingTokens.xxl // 20px
|
||||
SpacingTokens.xxxl // 24px
|
||||
SpacingTokens.huge // 32px
|
||||
|
||||
// Rayons de bordure
|
||||
SpacingTokens.radiusLg // 12px
|
||||
SpacingTokens.radiusMd // 8px
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Composants
|
||||
|
||||
### UFPrimaryButton
|
||||
|
||||
```dart
|
||||
UFPrimaryButton(
|
||||
label: 'Connexion',
|
||||
onPressed: () => login(),
|
||||
icon: Icons.login,
|
||||
isFullWidth: true,
|
||||
)
|
||||
```
|
||||
|
||||
### UFStatCard
|
||||
|
||||
```dart
|
||||
UFStatCard(
|
||||
title: 'Membres',
|
||||
value: '142',
|
||||
icon: Icons.people,
|
||||
iconColor: ColorTokens.primary,
|
||||
subtitle: '+5 ce mois',
|
||||
onTap: () => navigateToMembers(),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Règles d'Utilisation
|
||||
|
||||
### DO ✅
|
||||
|
||||
1. **TOUJOURS** utiliser `ColorTokens.*` pour les couleurs
|
||||
2. **TOUJOURS** utiliser `SpacingTokens.*` pour les espacements
|
||||
3. **TOUJOURS** utiliser les composants `UF*` quand disponibles
|
||||
|
||||
### DON'T ❌
|
||||
|
||||
1. **JAMAIS** définir de couleurs en dur (ex: `Color(0xFF...)`)
|
||||
2. **JAMAIS** définir d'espacements en dur (ex: `16.0`)
|
||||
3. **JAMAIS** créer de widgets custom sans vérifier les composants existants
|
||||
|
||||
---
|
||||
|
||||
**Dernière mise à jour**: 2025-10-05
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
# Guide du Design System UnionFlow
|
||||
|
||||
## 📋 Table des matières
|
||||
1. [Introduction](#introduction)
|
||||
2. [Tokens](#tokens)
|
||||
3. [Composants](#composants)
|
||||
4. [Bonnes pratiques](#bonnes-pratiques)
|
||||
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Le Design System UnionFlow garantit la cohérence visuelle et l'expérience utilisateur dans toute l'application.
|
||||
|
||||
**Palette de couleurs** : Bleu Roi (#4169E1) + Bleu Pétrole (#2C5F6F)
|
||||
**Basé sur** : Material Design 3 et tendances UI/UX 2024-2025
|
||||
|
||||
### Import
|
||||
|
||||
```dart
|
||||
import 'package:unionflow_mobile_apps/core/design_system/unionflow_design_system.dart';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tokens
|
||||
|
||||
### 🎨 Couleurs (ColorTokens)
|
||||
|
||||
```dart
|
||||
// Primaire
|
||||
ColorTokens.primary // Bleu Roi #4169E1
|
||||
ColorTokens.onPrimary // Blanc #FFFFFF
|
||||
ColorTokens.primaryContainer // Container bleu roi
|
||||
|
||||
// Sémantiques
|
||||
ColorTokens.success // Vert #10B981
|
||||
ColorTokens.error // Rouge #DC2626
|
||||
ColorTokens.warning // Orange #F59E0B
|
||||
ColorTokens.info // Bleu #0EA5E9
|
||||
|
||||
// Surfaces
|
||||
ColorTokens.surface // Blanc #FFFFFF
|
||||
ColorTokens.background // Gris clair #F8F9FA
|
||||
ColorTokens.onSurface // Texte principal #1F2937
|
||||
ColorTokens.onSurfaceVariant // Texte secondaire #6B7280
|
||||
|
||||
// Gradients
|
||||
ColorTokens.primaryGradient // [Bleu Roi, Bleu Roi clair]
|
||||
```
|
||||
|
||||
### 📏 Espacements (SpacingTokens)
|
||||
|
||||
```dart
|
||||
SpacingTokens.xs // 2px
|
||||
SpacingTokens.sm // 4px
|
||||
SpacingTokens.md // 8px
|
||||
SpacingTokens.lg // 12px
|
||||
SpacingTokens.xl // 16px
|
||||
SpacingTokens.xxl // 20px
|
||||
SpacingTokens.xxxl // 24px
|
||||
SpacingTokens.huge // 32px
|
||||
SpacingTokens.giant // 48px
|
||||
```
|
||||
|
||||
### 🔘 Rayons (SpacingTokens)
|
||||
|
||||
```dart
|
||||
SpacingTokens.radiusXs // 2px
|
||||
SpacingTokens.radiusSm // 4px
|
||||
SpacingTokens.radiusMd // 8px
|
||||
SpacingTokens.radiusLg // 12px - Standard pour cards
|
||||
SpacingTokens.radiusXl // 16px
|
||||
SpacingTokens.radiusXxl // 20px
|
||||
SpacingTokens.radiusCircular // 999px - Boutons ronds
|
||||
```
|
||||
|
||||
### 🌑 Ombres (ShadowTokens)
|
||||
|
||||
```dart
|
||||
ShadowTokens.xs // Ombre minimale
|
||||
ShadowTokens.sm // Ombre petite (cards, boutons)
|
||||
ShadowTokens.md // Ombre moyenne (cards importantes)
|
||||
ShadowTokens.lg // Ombre large (modals, dialogs)
|
||||
ShadowTokens.xl // Ombre très large (éléments flottants)
|
||||
|
||||
// Ombres colorées
|
||||
ShadowTokens.primary // Ombre avec couleur primaire
|
||||
ShadowTokens.success // Ombre verte
|
||||
ShadowTokens.error // Ombre rouge
|
||||
```
|
||||
|
||||
### ✍️ Typographie (TypographyTokens)
|
||||
|
||||
```dart
|
||||
TypographyTokens.displayLarge // 57px - Titres héroïques
|
||||
TypographyTokens.headlineLarge // 32px - Titres de page
|
||||
TypographyTokens.headlineMedium // 28px - Sous-titres
|
||||
TypographyTokens.titleLarge // 22px - Titres de section
|
||||
TypographyTokens.titleMedium // 16px - Titres de card
|
||||
TypographyTokens.bodyLarge // 16px - Corps de texte
|
||||
TypographyTokens.bodyMedium // 14px - Corps standard
|
||||
TypographyTokens.labelLarge // 14px - Labels
|
||||
TypographyTokens.labelSmall // 11px - Petits labels
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Composants
|
||||
|
||||
### 📦 UFCard - Card standardisé
|
||||
|
||||
```dart
|
||||
// Card avec ombre (par défaut)
|
||||
UFCard(
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
|
||||
// Card avec bordure
|
||||
UFCard.outlined(
|
||||
borderColor: ColorTokens.primary,
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
|
||||
// Card avec fond coloré
|
||||
UFCard.filled(
|
||||
color: ColorTokens.primaryContainer,
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
|
||||
// Card cliquable
|
||||
UFCard(
|
||||
onTap: () => print('Cliqué'),
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
```
|
||||
|
||||
### 📦 UFContainer - Container standardisé
|
||||
|
||||
```dart
|
||||
// Container standard
|
||||
UFContainer(
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
|
||||
// Container arrondi
|
||||
UFContainer.rounded(
|
||||
color: ColorTokens.primary,
|
||||
padding: EdgeInsets.all(SpacingTokens.lg),
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
|
||||
// Container avec ombre
|
||||
UFContainer.elevated(
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
|
||||
// Container circulaire
|
||||
UFContainer.circular(
|
||||
width: 48,
|
||||
height: 48,
|
||||
color: ColorTokens.primary,
|
||||
child: Icon(Icons.person),
|
||||
)
|
||||
```
|
||||
|
||||
### 📊 UFStatCard - Card de statistiques
|
||||
|
||||
```dart
|
||||
UFStatCard(
|
||||
title: 'Membres',
|
||||
value: '142',
|
||||
icon: Icons.people,
|
||||
iconColor: ColorTokens.primary,
|
||||
subtitle: '+5 ce mois',
|
||||
onTap: () => navigateToMembers(),
|
||||
)
|
||||
```
|
||||
|
||||
### ℹ️ UFInfoCard - Card d'information
|
||||
|
||||
```dart
|
||||
UFInfoCard(
|
||||
title: 'État du système',
|
||||
icon: Icons.health_and_safety,
|
||||
iconColor: ColorTokens.success,
|
||||
trailing: Badge(label: Text('OK')),
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Tous les systèmes fonctionnent normalement'),
|
||||
],
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### 🎯 UFHeader - Header de page
|
||||
|
||||
```dart
|
||||
UFHeader(
|
||||
title: 'Tableau de bord',
|
||||
subtitle: 'Vue d\'ensemble de votre activité',
|
||||
icon: Icons.dashboard,
|
||||
onNotificationTap: () => showNotifications(),
|
||||
onSettingsTap: () => showSettings(),
|
||||
)
|
||||
```
|
||||
|
||||
### 📱 UFAppBar - AppBar standardisé
|
||||
|
||||
```dart
|
||||
UFAppBar(
|
||||
title: 'Détails du membre',
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
onPressed: () => edit(),
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### 🔘 Boutons
|
||||
|
||||
```dart
|
||||
// Bouton primaire
|
||||
UFPrimaryButton(
|
||||
text: 'Enregistrer',
|
||||
onPressed: () => save(),
|
||||
icon: Icons.save,
|
||||
)
|
||||
|
||||
// Bouton secondaire
|
||||
UFSecondaryButton(
|
||||
text: 'Annuler',
|
||||
onPressed: () => cancel(),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bonnes pratiques
|
||||
|
||||
### ✅ À FAIRE
|
||||
|
||||
```dart
|
||||
// ✅ Utiliser les tokens
|
||||
Container(
|
||||
padding: EdgeInsets.all(SpacingTokens.xl),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorTokens.surface,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
boxShadow: ShadowTokens.sm,
|
||||
),
|
||||
)
|
||||
|
||||
// ✅ Utiliser les composants
|
||||
UFCard(
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
```
|
||||
|
||||
### ❌ À ÉVITER
|
||||
|
||||
```dart
|
||||
// ❌ Valeurs hardcodées
|
||||
Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFFFFFFF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
// ❌ Card Flutter standard
|
||||
Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
```
|
||||
|
||||
### 📐 Hiérarchie des espacements
|
||||
|
||||
- **xs/sm** : Éléments très proches (icône + texte)
|
||||
- **md/lg** : Espacement interne standard
|
||||
- **xl/xxl** : Espacement entre sections
|
||||
- **xxxl+** : Grandes séparations
|
||||
|
||||
### 🎨 Hiérarchie des couleurs
|
||||
|
||||
1. **primary** : Actions principales, navigation active
|
||||
2. **secondary** : Actions secondaires
|
||||
3. **success/error/warning** : États et feedbacks
|
||||
4. **surface/background** : Fonds et containers
|
||||
|
||||
### 🌑 Hiérarchie des ombres
|
||||
|
||||
- **xs/sm** : Cards et boutons standards
|
||||
- **md** : Cards importantes
|
||||
- **lg/xl** : Modals, dialogs, éléments flottants
|
||||
- **Colorées** : Éléments avec accent visuel
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Migration
|
||||
|
||||
Pour migrer du code existant :
|
||||
|
||||
1. Remplacer `Card` par `UFCard`
|
||||
2. Remplacer `Container` personnalisés par `UFContainer`
|
||||
3. Remplacer couleurs hardcodées par `ColorTokens`
|
||||
4. Remplacer espacements hardcodés par `SpacingTokens`
|
||||
5. Remplacer ombres personnalisées par `ShadowTokens`
|
||||
|
||||
**Exemple** :
|
||||
|
||||
```dart
|
||||
// Avant
|
||||
Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text('Contenu'),
|
||||
),
|
||||
)
|
||||
|
||||
// Après
|
||||
UFCard(
|
||||
child: Text('Contenu'),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version** : 1.0.0
|
||||
**Dernière mise à jour** : 2025-10-05
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/// UnionFlow Primary Button - Bouton principal
|
||||
///
|
||||
/// Bouton primaire avec la couleur Bleu Roi (#4169E1)
|
||||
/// Utilisé pour les actions principales (connexion, enregistrer, valider, etc.)
|
||||
library uf_primary_button;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../tokens/color_tokens.dart';
|
||||
import '../../tokens/spacing_tokens.dart';
|
||||
import '../../tokens/typography_tokens.dart';
|
||||
|
||||
/// Bouton primaire UnionFlow
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFPrimaryButton(
|
||||
/// label: 'Connexion',
|
||||
/// onPressed: () => login(),
|
||||
/// icon: Icons.login,
|
||||
/// isLoading: false,
|
||||
/// )
|
||||
/// ```
|
||||
class UFPrimaryButton extends StatelessWidget {
|
||||
/// Texte du bouton
|
||||
final String label;
|
||||
|
||||
/// Callback appelé lors du clic
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// Indique si le bouton est en chargement
|
||||
final bool isLoading;
|
||||
|
||||
/// Icône optionnelle à gauche du texte
|
||||
final IconData? icon;
|
||||
|
||||
/// Bouton pleine largeur
|
||||
final bool isFullWidth;
|
||||
|
||||
/// Hauteur personnalisée (optionnel)
|
||||
final double? height;
|
||||
|
||||
const UFPrimaryButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.onPressed,
|
||||
this.isLoading = false,
|
||||
this.icon,
|
||||
this.isFullWidth = false,
|
||||
this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: isFullWidth ? double.infinity : null,
|
||||
height: height ?? SpacingTokens.buttonHeightLarge,
|
||||
child: ElevatedButton(
|
||||
onPressed: isLoading ? null : onPressed,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ColorTokens.primary, // Bleu roi
|
||||
foregroundColor: ColorTokens.onPrimary, // Blanc
|
||||
disabledBackgroundColor: ColorTokens.primary.withOpacity(0.5),
|
||||
disabledForegroundColor: ColorTokens.onPrimary.withOpacity(0.7),
|
||||
elevation: SpacingTokens.elevationSm,
|
||||
shadowColor: ColorTokens.shadow,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.buttonPaddingHorizontal,
|
||||
vertical: SpacingTokens.buttonPaddingVertical,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
ColorTokens.onPrimary,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(icon, size: 20),
|
||||
SizedBox(width: SpacingTokens.md),
|
||||
],
|
||||
Text(
|
||||
label,
|
||||
style: TypographyTokens.buttonLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/// UnionFlow Secondary Button - Bouton secondaire
|
||||
///
|
||||
/// Bouton secondaire avec la couleur Indigo (#6366F1)
|
||||
/// Utilisé pour les actions secondaires (annuler, retour, etc.)
|
||||
library uf_secondary_button;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../tokens/color_tokens.dart';
|
||||
import '../../tokens/spacing_tokens.dart';
|
||||
import '../../tokens/typography_tokens.dart';
|
||||
|
||||
/// Bouton secondaire UnionFlow
|
||||
class UFSecondaryButton extends StatelessWidget {
|
||||
final String label;
|
||||
final VoidCallback? onPressed;
|
||||
final bool isLoading;
|
||||
final IconData? icon;
|
||||
final bool isFullWidth;
|
||||
final double? height;
|
||||
|
||||
const UFSecondaryButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.onPressed,
|
||||
this.isLoading = false,
|
||||
this.icon,
|
||||
this.isFullWidth = false,
|
||||
this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: isFullWidth ? double.infinity : null,
|
||||
height: height ?? SpacingTokens.buttonHeightLarge,
|
||||
child: ElevatedButton(
|
||||
onPressed: isLoading ? null : onPressed,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ColorTokens.secondary, // Indigo
|
||||
foregroundColor: ColorTokens.onSecondary, // Blanc
|
||||
disabledBackgroundColor: ColorTokens.secondary.withOpacity(0.5),
|
||||
disabledForegroundColor: ColorTokens.onSecondary.withOpacity(0.7),
|
||||
elevation: SpacingTokens.elevationSm,
|
||||
shadowColor: ColorTokens.shadow,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.buttonPaddingHorizontal,
|
||||
vertical: SpacingTokens.buttonPaddingVertical,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
ColorTokens.onSecondary,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(icon, size: 20),
|
||||
SizedBox(width: SpacingTokens.md),
|
||||
],
|
||||
Text(
|
||||
label,
|
||||
style: TypographyTokens.buttonLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../unionflow_design_system.dart';
|
||||
|
||||
/// Card standardisé UnionFlow
|
||||
///
|
||||
/// Composant Card unifié avec 3 styles prédéfinis :
|
||||
/// - elevated : Card avec ombre (par défaut)
|
||||
/// - outlined : Card avec bordure
|
||||
/// - filled : Card avec fond coloré
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFCard(
|
||||
/// child: Text('Contenu'),
|
||||
/// )
|
||||
///
|
||||
/// UFCard.outlined(
|
||||
/// child: Text('Contenu'),
|
||||
/// )
|
||||
///
|
||||
/// UFCard.filled(
|
||||
/// color: ColorTokens.primary,
|
||||
/// child: Text('Contenu'),
|
||||
/// )
|
||||
/// ```
|
||||
class UFCard extends StatelessWidget {
|
||||
final Widget child;
|
||||
final EdgeInsets? padding;
|
||||
final EdgeInsets? margin;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
final UFCardStyle style;
|
||||
final Color? color;
|
||||
final Color? borderColor;
|
||||
final double? borderWidth;
|
||||
final double? elevation;
|
||||
final double? borderRadius;
|
||||
|
||||
/// Card avec ombre (style par défaut)
|
||||
const UFCard({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.color,
|
||||
this.elevation,
|
||||
this.borderRadius,
|
||||
}) : style = UFCardStyle.elevated,
|
||||
borderColor = null,
|
||||
borderWidth = null;
|
||||
|
||||
/// Card avec bordure
|
||||
const UFCard.outlined({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.color,
|
||||
this.borderColor,
|
||||
this.borderWidth,
|
||||
this.borderRadius,
|
||||
}) : style = UFCardStyle.outlined,
|
||||
elevation = null;
|
||||
|
||||
/// Card avec fond coloré
|
||||
const UFCard.filled({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
required this.color,
|
||||
this.borderRadius,
|
||||
}) : style = UFCardStyle.filled,
|
||||
borderColor = null,
|
||||
borderWidth = null,
|
||||
elevation = null;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectivePadding = padding ?? EdgeInsets.all(SpacingTokens.cardPadding);
|
||||
final effectiveMargin = margin ?? EdgeInsets.zero;
|
||||
final effectiveBorderRadius = borderRadius ?? SpacingTokens.radiusLg;
|
||||
|
||||
Widget content = Container(
|
||||
padding: effectivePadding,
|
||||
decoration: _getDecoration(effectiveBorderRadius),
|
||||
child: child,
|
||||
);
|
||||
|
||||
if (onTap != null || onLongPress != null) {
|
||||
content = InkWell(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
borderRadius: BorderRadius.circular(effectiveBorderRadius),
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: effectiveMargin,
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
|
||||
BoxDecoration _getDecoration(double radius) {
|
||||
switch (style) {
|
||||
case UFCardStyle.elevated:
|
||||
return BoxDecoration(
|
||||
color: color ?? ColorTokens.surface,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: elevation ?? SpacingTokens.elevationSm * 5, // 10
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
case UFCardStyle.outlined:
|
||||
return BoxDecoration(
|
||||
color: color ?? ColorTokens.surface,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
border: Border.all(
|
||||
color: borderColor ?? ColorTokens.outline,
|
||||
width: borderWidth ?? 1.0,
|
||||
),
|
||||
);
|
||||
|
||||
case UFCardStyle.filled:
|
||||
return BoxDecoration(
|
||||
color: color ?? ColorTokens.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Styles de Card disponibles
|
||||
enum UFCardStyle {
|
||||
/// Card avec ombre
|
||||
elevated,
|
||||
|
||||
/// Card avec bordure
|
||||
outlined,
|
||||
|
||||
/// Card avec fond coloré
|
||||
filled,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/// UnionFlow Info Card - Card d'information générique
|
||||
///
|
||||
/// Card blanche avec titre, icône et contenu personnalisable
|
||||
library uf_info_card;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../tokens/color_tokens.dart';
|
||||
import '../../tokens/spacing_tokens.dart';
|
||||
import '../../tokens/typography_tokens.dart';
|
||||
|
||||
/// Card d'information générique
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFInfoCard(
|
||||
/// title: 'État du système',
|
||||
/// icon: Icons.health_and_safety,
|
||||
/// iconColor: ColorTokens.primary,
|
||||
/// trailing: Container(...), // Badge ou autre widget
|
||||
/// child: Column(...), // Contenu de la card
|
||||
/// )
|
||||
/// ```
|
||||
class UFInfoCard extends StatelessWidget {
|
||||
/// Titre de la card
|
||||
final String title;
|
||||
|
||||
/// Icône du titre
|
||||
final IconData icon;
|
||||
|
||||
/// Couleur de l'icône (par défaut: primary)
|
||||
final Color? iconColor;
|
||||
|
||||
/// Widget à droite du titre (badge, bouton, etc.)
|
||||
final Widget? trailing;
|
||||
|
||||
/// Contenu de la card
|
||||
final Widget child;
|
||||
|
||||
/// Padding de la card (par défaut: xl)
|
||||
final EdgeInsets? padding;
|
||||
|
||||
const UFInfoCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
this.iconColor,
|
||||
this.trailing,
|
||||
required this.child,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveIconColor = iconColor ?? ColorTokens.primary;
|
||||
final effectivePadding = padding ?? EdgeInsets.all(SpacingTokens.xl);
|
||||
|
||||
return Container(
|
||||
padding: effectivePadding,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorTokens.surface,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusXl),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header avec titre et trailing
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, color: effectiveIconColor, size: 20),
|
||||
SizedBox(width: SpacingTokens.md),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TypographyTokens.titleMedium.copyWith(
|
||||
color: ColorTokens.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
SizedBox(height: SpacingTokens.xl),
|
||||
// Contenu
|
||||
child,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/// UnionFlow Metric Card - Card de métrique système
|
||||
///
|
||||
/// Card compacte pour afficher une métrique système (CPU, RAM, etc.)
|
||||
library uf_metric_card;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../tokens/color_tokens.dart';
|
||||
import '../../tokens/spacing_tokens.dart';
|
||||
import '../../tokens/typography_tokens.dart';
|
||||
|
||||
/// Card de métrique système
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFMetricCard(
|
||||
/// label: 'CPU',
|
||||
/// value: '23.5%',
|
||||
/// icon: Icons.memory,
|
||||
/// color: ColorTokens.success,
|
||||
/// )
|
||||
/// ```
|
||||
class UFMetricCard extends StatelessWidget {
|
||||
/// Label de la métrique (ex: "CPU")
|
||||
final String label;
|
||||
|
||||
/// Valeur de la métrique (ex: "23.5%")
|
||||
final String value;
|
||||
|
||||
/// Icône représentant la métrique
|
||||
final IconData icon;
|
||||
|
||||
/// Couleur de la métrique (optionnel)
|
||||
final Color? color;
|
||||
|
||||
const UFMetricCard({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.icon,
|
||||
this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(SpacingTokens.md),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: Colors.white, size: 16),
|
||||
SizedBox(height: SpacingTokens.sm),
|
||||
Text(
|
||||
value,
|
||||
style: TypographyTokens.labelSmall.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: TypographyTokens.labelSmall.copyWith(
|
||||
fontSize: 9,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/// UnionFlow Stat Card - Card de statistiques
|
||||
///
|
||||
/// Card affichant une statistique avec icône, titre, valeur et sous-titre optionnel
|
||||
/// Utilisé dans le dashboard pour afficher les métriques clés
|
||||
library uf_stat_card;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../tokens/color_tokens.dart';
|
||||
import '../../tokens/spacing_tokens.dart';
|
||||
import '../../tokens/typography_tokens.dart';
|
||||
|
||||
/// Card de statistiques UnionFlow
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFStatCard(
|
||||
/// title: 'Membres',
|
||||
/// value: '142',
|
||||
/// icon: Icons.people,
|
||||
/// iconColor: ColorTokens.primary,
|
||||
/// subtitle: '+5 ce mois',
|
||||
/// onTap: () => navigateToMembers(),
|
||||
/// )
|
||||
/// ```
|
||||
class UFStatCard extends StatelessWidget {
|
||||
/// Titre de la statistique (ex: "Membres")
|
||||
final String title;
|
||||
|
||||
/// Valeur de la statistique (ex: "142")
|
||||
final String value;
|
||||
|
||||
/// Icône représentant la statistique
|
||||
final IconData icon;
|
||||
|
||||
/// Couleur de l'icône (par défaut: primary)
|
||||
final Color? iconColor;
|
||||
|
||||
/// Sous-titre optionnel (ex: "+5 ce mois")
|
||||
final String? subtitle;
|
||||
|
||||
/// Callback appelé lors du clic sur la card
|
||||
final VoidCallback? onTap;
|
||||
|
||||
/// Couleur de fond de l'icône (par défaut: iconColor avec opacité)
|
||||
final Color? iconBackgroundColor;
|
||||
|
||||
const UFStatCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.icon,
|
||||
this.iconColor,
|
||||
this.subtitle,
|
||||
this.onTap,
|
||||
this.iconBackgroundColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveIconColor = iconColor ?? ColorTokens.primary;
|
||||
final effectiveIconBgColor = iconBackgroundColor ??
|
||||
effectiveIconColor.withOpacity(0.1);
|
||||
|
||||
return Card(
|
||||
elevation: SpacingTokens.elevationSm,
|
||||
shadowColor: ColorTokens.shadow,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(SpacingTokens.cardPadding),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header avec icône et flèche
|
||||
Row(
|
||||
children: [
|
||||
// Icône avec background coloré
|
||||
Container(
|
||||
padding: EdgeInsets.all(SpacingTokens.md),
|
||||
decoration: BoxDecoration(
|
||||
color: effectiveIconBgColor,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: effectiveIconColor,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
// Flèche si cliquable
|
||||
if (onTap != null)
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: SpacingTokens.lg),
|
||||
|
||||
// Titre
|
||||
Text(
|
||||
title,
|
||||
style: TypographyTokens.labelLarge.copyWith(
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: SpacingTokens.sm),
|
||||
|
||||
// Valeur
|
||||
Text(
|
||||
value,
|
||||
style: TypographyTokens.cardValue.copyWith(
|
||||
color: ColorTokens.onSurface,
|
||||
),
|
||||
),
|
||||
|
||||
// Sous-titre optionnel
|
||||
if (subtitle != null) ...[
|
||||
SizedBox(height: SpacingTokens.sm),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: TypographyTokens.bodySmall.copyWith(
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/// UnionFlow Components - Export centralisé
|
||||
///
|
||||
/// Ce fichier exporte tous les composants réutilisables du Design System
|
||||
library components;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// BOUTONS
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export 'buttons/uf_primary_button.dart';
|
||||
export 'buttons/uf_secondary_button.dart';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// CARDS
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export 'cards/uf_stat_card.dart';
|
||||
|
||||
// TODO: Ajouter d'autres composants au fur et à mesure
|
||||
// export 'buttons/uf_outline_button.dart';
|
||||
// export 'buttons/uf_text_button.dart';
|
||||
// export 'cards/uf_event_card.dart';
|
||||
// export 'cards/uf_info_card.dart';
|
||||
// export 'inputs/uf_text_field.dart';
|
||||
// export 'navigation/uf_app_bar.dart';
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/// UnionFlow Dropdown Tile - Ligne de paramètre avec dropdown
|
||||
///
|
||||
/// Tile avec titre et dropdown pour les paramètres
|
||||
library uf_dropdown_tile;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../tokens/color_tokens.dart';
|
||||
import '../../tokens/spacing_tokens.dart';
|
||||
import '../../tokens/typography_tokens.dart';
|
||||
|
||||
/// Tile de paramètre avec dropdown
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFDropdownTile<String>(
|
||||
/// title: 'Niveau de log',
|
||||
/// value: 'INFO',
|
||||
/// items: ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'],
|
||||
/// onChanged: (value) => setState(() => _logLevel = value),
|
||||
/// )
|
||||
/// ```
|
||||
class UFDropdownTile<T> extends StatelessWidget {
|
||||
/// Titre du paramètre
|
||||
final String title;
|
||||
|
||||
/// Valeur actuelle
|
||||
final T value;
|
||||
|
||||
/// Liste des options
|
||||
final List<T> items;
|
||||
|
||||
/// Callback appelé lors du changement
|
||||
final ValueChanged<T?>? onChanged;
|
||||
|
||||
/// Couleur de fond (par défaut: surfaceVariant)
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Fonction pour afficher le texte d'un item (par défaut: toString())
|
||||
final String Function(T)? itemBuilder;
|
||||
|
||||
const UFDropdownTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.items,
|
||||
this.onChanged,
|
||||
this.backgroundColor,
|
||||
this.itemBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveBgColor = backgroundColor ?? ColorTokens.surfaceVariant;
|
||||
final effectiveItemBuilder = itemBuilder ?? (item) => item.toString();
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: SpacingTokens.lg),
|
||||
padding: EdgeInsets.all(SpacingTokens.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: effectiveBgColor,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TypographyTokens.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorTokens.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: SpacingTokens.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorTokens.surface,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
border: Border.all(color: ColorTokens.outline),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<T>(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
items: items.map((item) {
|
||||
return DropdownMenuItem<T>(
|
||||
value: item,
|
||||
child: Text(effectiveItemBuilder(item)),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/// UnionFlow Switch Tile - Ligne de paramètre avec switch
|
||||
///
|
||||
/// Tile avec titre, description et switch pour les paramètres
|
||||
library uf_switch_tile;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../tokens/color_tokens.dart';
|
||||
import '../../tokens/spacing_tokens.dart';
|
||||
import '../../tokens/typography_tokens.dart';
|
||||
|
||||
/// Tile de paramètre avec switch
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFSwitchTile(
|
||||
/// title: 'Notifications',
|
||||
/// subtitle: 'Activer les notifications push',
|
||||
/// value: true,
|
||||
/// onChanged: (value) => setState(() => _notifications = value),
|
||||
/// )
|
||||
/// ```
|
||||
class UFSwitchTile extends StatelessWidget {
|
||||
/// Titre du paramètre
|
||||
final String title;
|
||||
|
||||
/// Description du paramètre
|
||||
final String subtitle;
|
||||
|
||||
/// Valeur actuelle du switch
|
||||
final bool value;
|
||||
|
||||
/// Callback appelé lors du changement
|
||||
final ValueChanged<bool>? onChanged;
|
||||
|
||||
/// Couleur de fond (par défaut: surfaceVariant)
|
||||
final Color? backgroundColor;
|
||||
|
||||
const UFSwitchTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.value,
|
||||
this.onChanged,
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveBgColor = backgroundColor ?? ColorTokens.surfaceVariant;
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: SpacingTokens.lg),
|
||||
padding: EdgeInsets.all(SpacingTokens.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: effectiveBgColor,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TypographyTokens.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorTokens.onSurface,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TypographyTokens.bodySmall.copyWith(
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: ColorTokens.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../unionflow_design_system.dart';
|
||||
|
||||
/// AppBar standardisé UnionFlow
|
||||
///
|
||||
/// Composant AppBar unifié pour toutes les pages de détail/formulaire.
|
||||
/// Garantit la cohérence visuelle et l'expérience utilisateur.
|
||||
class UFAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final String title;
|
||||
final List<Widget>? actions;
|
||||
final Widget? leading;
|
||||
final bool automaticallyImplyLeading;
|
||||
final PreferredSizeWidget? bottom;
|
||||
final Color? backgroundColor;
|
||||
final Color? foregroundColor;
|
||||
final double elevation;
|
||||
|
||||
const UFAppBar({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.actions,
|
||||
this.leading,
|
||||
this.automaticallyImplyLeading = true,
|
||||
this.bottom,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.elevation = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
title: Text(title),
|
||||
backgroundColor: backgroundColor ?? ColorTokens.primary,
|
||||
foregroundColor: foregroundColor ?? ColorTokens.onPrimary,
|
||||
elevation: elevation,
|
||||
leading: leading,
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
actions: actions,
|
||||
bottom: bottom,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.light, // Icônes claires sur fond bleu
|
||||
statusBarBrightness: Brightness.dark, // Pour iOS
|
||||
),
|
||||
centerTitle: false,
|
||||
titleTextStyle: TypographyTokens.titleLarge.copyWith(
|
||||
color: foregroundColor ?? ColorTokens.onPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(
|
||||
kToolbarHeight + (bottom?.preferredSize.height ?? 0.0),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../unionflow_design_system.dart';
|
||||
|
||||
/// Container standardisé UnionFlow
|
||||
///
|
||||
/// Composant Container unifié avec styles prédéfinis.
|
||||
/// Garantit la cohérence des espacements, rayons et ombres.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFContainer(
|
||||
/// child: Text('Contenu'),
|
||||
/// )
|
||||
///
|
||||
/// UFContainer.rounded(
|
||||
/// color: ColorTokens.primary,
|
||||
/// child: Text('Contenu'),
|
||||
/// )
|
||||
///
|
||||
/// UFContainer.elevated(
|
||||
/// child: Text('Contenu'),
|
||||
/// )
|
||||
/// ```
|
||||
class UFContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Color? color;
|
||||
final EdgeInsets? padding;
|
||||
final EdgeInsets? margin;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final AlignmentGeometry? alignment;
|
||||
final BoxConstraints? constraints;
|
||||
final Gradient? gradient;
|
||||
final double borderRadius;
|
||||
final Border? border;
|
||||
final List<BoxShadow>? boxShadow;
|
||||
|
||||
/// Container standard
|
||||
const UFContainer({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.color,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.width,
|
||||
this.height,
|
||||
this.alignment,
|
||||
this.constraints,
|
||||
this.gradient,
|
||||
this.border,
|
||||
this.boxShadow,
|
||||
}) : borderRadius = SpacingTokens.radiusMd;
|
||||
|
||||
/// Container avec coins arrondis
|
||||
const UFContainer.rounded({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.color,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.width,
|
||||
this.height,
|
||||
this.alignment,
|
||||
this.constraints,
|
||||
this.gradient,
|
||||
this.border,
|
||||
this.boxShadow,
|
||||
}) : borderRadius = SpacingTokens.radiusLg;
|
||||
|
||||
/// Container très arrondi
|
||||
const UFContainer.extraRounded({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.color,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.width,
|
||||
this.height,
|
||||
this.alignment,
|
||||
this.constraints,
|
||||
this.gradient,
|
||||
this.border,
|
||||
this.boxShadow,
|
||||
}) : borderRadius = SpacingTokens.radiusXl;
|
||||
|
||||
/// Container avec ombre
|
||||
UFContainer.elevated({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.color,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.width,
|
||||
this.height,
|
||||
this.alignment,
|
||||
this.constraints,
|
||||
this.gradient,
|
||||
this.border,
|
||||
}) : borderRadius = SpacingTokens.radiusLg,
|
||||
boxShadow = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
];
|
||||
|
||||
/// Container circulaire
|
||||
const UFContainer.circular({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.color,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.width,
|
||||
this.height,
|
||||
this.alignment,
|
||||
this.constraints,
|
||||
this.gradient,
|
||||
this.border,
|
||||
this.boxShadow,
|
||||
}) : borderRadius = SpacingTokens.radiusCircular;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: width,
|
||||
height: height,
|
||||
padding: padding,
|
||||
margin: margin,
|
||||
alignment: alignment,
|
||||
constraints: constraints,
|
||||
decoration: BoxDecoration(
|
||||
color: gradient == null ? (color ?? ColorTokens.surface) : null,
|
||||
gradient: gradient,
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
border: border,
|
||||
boxShadow: boxShadow,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../unionflow_design_system.dart';
|
||||
|
||||
/// Header de page compact et moderne
|
||||
///
|
||||
/// Composant header minimaliste pour les pages principales du BottomNavigationBar.
|
||||
/// Design épuré sans gradient lourd, optimisé pour l'espace.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFPageHeader(
|
||||
/// title: 'Membres',
|
||||
/// icon: Icons.people,
|
||||
/// actions: [
|
||||
/// IconButton(icon: Icon(Icons.add), onPressed: () {}),
|
||||
/// ],
|
||||
/// )
|
||||
/// ```
|
||||
class UFPageHeader extends StatelessWidget {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final List<Widget>? actions;
|
||||
final Color? iconColor;
|
||||
final bool showDivider;
|
||||
|
||||
const UFPageHeader({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
this.actions,
|
||||
this.iconColor,
|
||||
this.showDivider = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveIconColor = iconColor ?? ColorTokens.primary;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.lg,
|
||||
vertical: SpacingTokens.md,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icône
|
||||
Container(
|
||||
padding: EdgeInsets.all(SpacingTokens.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: effectiveIconColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: effectiveIconColor,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
SizedBox(width: SpacingTokens.md),
|
||||
|
||||
// Titre
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TypographyTokens.titleLarge.copyWith(
|
||||
color: ColorTokens.onSurface,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Actions
|
||||
if (actions != null) ...actions!,
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Divider optionnel
|
||||
if (showDivider)
|
||||
Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: ColorTokens.outline.withOpacity(0.1),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Header de page avec statistiques
|
||||
///
|
||||
/// Header compact avec des métriques KPI intégrées.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// UFPageHeaderWithStats(
|
||||
/// title: 'Membres',
|
||||
/// icon: Icons.people,
|
||||
/// stats: [
|
||||
/// UFHeaderStat(label: 'Total', value: '142'),
|
||||
/// UFHeaderStat(label: 'Actifs', value: '128'),
|
||||
/// ],
|
||||
/// )
|
||||
/// ```
|
||||
class UFPageHeaderWithStats extends StatelessWidget {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final List<UFHeaderStat> stats;
|
||||
final List<Widget>? actions;
|
||||
final Color? iconColor;
|
||||
|
||||
const UFPageHeaderWithStats({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.stats,
|
||||
this.actions,
|
||||
this.iconColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveIconColor = iconColor ?? ColorTokens.primary;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Titre et actions
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
SpacingTokens.lg,
|
||||
SpacingTokens.md,
|
||||
SpacingTokens.lg,
|
||||
SpacingTokens.sm,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icône
|
||||
Container(
|
||||
padding: EdgeInsets.all(SpacingTokens.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: effectiveIconColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: effectiveIconColor,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
SizedBox(width: SpacingTokens.md),
|
||||
|
||||
// Titre
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TypographyTokens.titleLarge.copyWith(
|
||||
color: ColorTokens.onSurface,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Actions
|
||||
if (actions != null) ...actions!,
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Statistiques
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
SpacingTokens.lg,
|
||||
0,
|
||||
SpacingTokens.lg,
|
||||
SpacingTokens.md,
|
||||
),
|
||||
child: Row(
|
||||
children: stats.map((stat) {
|
||||
final isLast = stat == stats.last;
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
right: isLast ? 0 : SpacingTokens.sm,
|
||||
),
|
||||
child: _buildStatItem(stat),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
|
||||
// Divider
|
||||
Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: ColorTokens.outline.withOpacity(0.1),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatItem(UFHeaderStat stat) {
|
||||
return UFContainer.rounded(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.md,
|
||||
vertical: SpacingTokens.sm,
|
||||
),
|
||||
color: (stat.color ?? ColorTokens.primary).withOpacity(0.05),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
stat.value,
|
||||
style: TypographyTokens.titleMedium.copyWith(
|
||||
color: stat.color ?? ColorTokens.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: SpacingTokens.xs),
|
||||
Text(
|
||||
stat.label,
|
||||
style: TypographyTokens.labelSmall.copyWith(
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistique pour UFPageHeaderWithStats
|
||||
class UFHeaderStat {
|
||||
final String label;
|
||||
final String value;
|
||||
final Color? color;
|
||||
|
||||
const UFHeaderStat({
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.color,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Design System UnionFlow - Tokens de design centralisés
|
||||
///
|
||||
/// Ce fichier centralise tous les tokens de design pour garantir
|
||||
/// la cohérence visuelle dans toute l'application UnionFlow.
|
||||
class UnionFlowDesignTokens {
|
||||
// ==================== COULEURS ====================
|
||||
|
||||
/// Couleurs primaires
|
||||
static const Color primaryColor = Color(0xFF6C5CE7);
|
||||
static const Color primaryDark = Color(0xFF5A4FCF);
|
||||
static const Color primaryLight = Color(0xFF8B7EE8);
|
||||
|
||||
/// Couleurs secondaires
|
||||
static const Color secondaryColor = Color(0xFF0984E3);
|
||||
static const Color secondaryDark = Color(0xFF0770C2);
|
||||
static const Color secondaryLight = Color(0xFF3498E8);
|
||||
|
||||
/// Couleurs de statut
|
||||
static const Color successColor = Color(0xFF00B894);
|
||||
static const Color warningColor = Color(0xFFE17055);
|
||||
static const Color errorColor = Color(0xFFE74C3C);
|
||||
static const Color infoColor = Color(0xFF00CEC9);
|
||||
|
||||
/// Couleurs neutres
|
||||
static const Color backgroundColor = Color(0xFFF8F9FA);
|
||||
static const Color surfaceColor = Colors.white;
|
||||
static const Color cardColor = Colors.white;
|
||||
|
||||
/// Couleurs de texte
|
||||
static const Color textPrimary = Color(0xFF1F2937);
|
||||
static const Color textSecondary = Color(0xFF6B7280);
|
||||
static const Color textTertiary = Color(0xFF9CA3AF);
|
||||
static const Color textOnPrimary = Colors.white;
|
||||
|
||||
/// Couleurs de bordure
|
||||
static const Color borderLight = Color(0xFFE5E7EB);
|
||||
static const Color borderMedium = Color(0xFFD1D5DB);
|
||||
static const Color borderDark = Color(0xFF9CA3AF);
|
||||
|
||||
// ==================== TYPOGRAPHIE ====================
|
||||
|
||||
/// Tailles de police
|
||||
static const double fontSizeXS = 10.0;
|
||||
static const double fontSizeSM = 12.0;
|
||||
static const double fontSizeBase = 14.0;
|
||||
static const double fontSizeLG = 16.0;
|
||||
static const double fontSizeXL = 18.0;
|
||||
static const double fontSize2XL = 20.0;
|
||||
static const double fontSize3XL = 24.0;
|
||||
static const double fontSize4XL = 28.0;
|
||||
|
||||
/// Poids de police
|
||||
static const FontWeight fontWeightNormal = FontWeight.w400;
|
||||
static const FontWeight fontWeightMedium = FontWeight.w500;
|
||||
static const FontWeight fontWeightSemiBold = FontWeight.w600;
|
||||
static const FontWeight fontWeightBold = FontWeight.w700;
|
||||
|
||||
// ==================== ESPACEMENT ====================
|
||||
|
||||
/// Espacements
|
||||
static const double spaceXS = 4.0;
|
||||
static const double spaceSM = 8.0;
|
||||
static const double spaceBase = 12.0;
|
||||
static const double spaceMD = 16.0;
|
||||
static const double spaceLG = 20.0;
|
||||
static const double spaceXL = 24.0;
|
||||
static const double space2XL = 32.0;
|
||||
static const double space3XL = 48.0;
|
||||
|
||||
// ==================== RAYONS DE BORDURE ====================
|
||||
|
||||
/// Rayons de bordure
|
||||
static const double radiusXS = 4.0;
|
||||
static const double radiusSM = 8.0;
|
||||
static const double radiusBase = 12.0;
|
||||
static const double radiusLG = 16.0;
|
||||
static const double radiusXL = 20.0;
|
||||
static const double radiusFull = 999.0;
|
||||
|
||||
// ==================== OMBRES ====================
|
||||
|
||||
/// Ombres prédéfinies
|
||||
static List<BoxShadow> get shadowSM => [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 5,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
];
|
||||
|
||||
static List<BoxShadow> get shadowBase => [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
];
|
||||
|
||||
static List<BoxShadow> get shadowLG => [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
static List<BoxShadow> get shadowXL => [
|
||||
BoxShadow(
|
||||
color: primaryColor.withOpacity(0.3),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
];
|
||||
|
||||
// ==================== GRADIENTS ====================
|
||||
|
||||
/// Gradients prédéfinis
|
||||
static const LinearGradient primaryGradient = LinearGradient(
|
||||
colors: [primaryColor, primaryDark],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
|
||||
static const LinearGradient secondaryGradient = LinearGradient(
|
||||
colors: [secondaryColor, secondaryDark],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
|
||||
// ==================== STYLES DE TEXTE ====================
|
||||
|
||||
/// Styles de texte prédéfinis
|
||||
static const TextStyle headingXL = TextStyle(
|
||||
fontSize: fontSize3XL,
|
||||
fontWeight: fontWeightBold,
|
||||
color: textPrimary,
|
||||
height: 1.2,
|
||||
);
|
||||
|
||||
static const TextStyle headingLG = TextStyle(
|
||||
fontSize: fontSize2XL,
|
||||
fontWeight: fontWeightBold,
|
||||
color: textPrimary,
|
||||
height: 1.3,
|
||||
);
|
||||
|
||||
static const TextStyle headingMD = TextStyle(
|
||||
fontSize: fontSizeXL,
|
||||
fontWeight: fontWeightSemiBold,
|
||||
color: textPrimary,
|
||||
height: 1.4,
|
||||
);
|
||||
|
||||
static const TextStyle bodySM = TextStyle(
|
||||
fontSize: fontSizeSM,
|
||||
fontWeight: fontWeightNormal,
|
||||
color: textSecondary,
|
||||
height: 1.5,
|
||||
);
|
||||
|
||||
static const TextStyle bodyBase = TextStyle(
|
||||
fontSize: fontSizeBase,
|
||||
fontWeight: fontWeightNormal,
|
||||
color: textPrimary,
|
||||
height: 1.5,
|
||||
);
|
||||
|
||||
static const TextStyle bodyLG = TextStyle(
|
||||
fontSize: fontSizeLG,
|
||||
fontWeight: fontWeightNormal,
|
||||
color: textPrimary,
|
||||
height: 1.5,
|
||||
);
|
||||
|
||||
static const TextStyle caption = TextStyle(
|
||||
fontSize: fontSizeXS,
|
||||
fontWeight: fontWeightNormal,
|
||||
color: textTertiary,
|
||||
height: 1.4,
|
||||
);
|
||||
|
||||
static const TextStyle buttonText = TextStyle(
|
||||
fontSize: fontSizeBase,
|
||||
fontWeight: fontWeightSemiBold,
|
||||
color: textOnPrimary,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/// Tokens d'ombres pour le design system
|
||||
/// Définit les ombres standardisées de l'application
|
||||
library shadow_tokens;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'color_tokens.dart';
|
||||
|
||||
/// Tokens d'ombres standardisés
|
||||
///
|
||||
/// Utilisation cohérente des ombres dans toute l'application.
|
||||
/// Basé sur les principes de Material Design 3.
|
||||
class ShadowTokens {
|
||||
ShadowTokens._();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// OMBRES STANDARDS
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Ombre minimale - Pour éléments subtils
|
||||
static final List<BoxShadow> xs = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre petite - Pour cards et boutons
|
||||
static final List<BoxShadow> sm = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre moyenne - Pour cards importantes
|
||||
static final List<BoxShadow> md = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre large - Pour modals et dialogs
|
||||
static final List<BoxShadow> lg = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadowMedium,
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre très large - Pour éléments flottants
|
||||
static final List<BoxShadow> xl = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadowMedium,
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre extra large - Pour éléments héroïques
|
||||
static final List<BoxShadow> xxl = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadowHigh,
|
||||
blurRadius: 32,
|
||||
offset: const Offset(0, 12),
|
||||
spreadRadius: -4,
|
||||
),
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// OMBRES COLORÉES
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Ombre primaire - Pour éléments avec couleur primaire
|
||||
static final List<BoxShadow> primary = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.primary.withOpacity(0.15),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre secondaire - Pour éléments avec couleur secondaire
|
||||
static final List<BoxShadow> secondary = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.secondary.withOpacity(0.15),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre success - Pour éléments de succès
|
||||
static final List<BoxShadow> success = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.success.withOpacity(0.15),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre error - Pour éléments d'erreur
|
||||
static final List<BoxShadow> error = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.error.withOpacity(0.15),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre warning - Pour éléments d'avertissement
|
||||
static final List<BoxShadow> warning = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.warning.withOpacity(0.15),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// OMBRES SPÉCIALES
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Ombre interne - Pour effets enfoncés
|
||||
static final List<BoxShadow> inner = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
spreadRadius: -2,
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre diffuse - Pour glassmorphism
|
||||
static final List<BoxShadow> diffuse = [
|
||||
BoxShadow(
|
||||
color: ColorTokens.shadow.withOpacity(0.05),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 4),
|
||||
spreadRadius: 2,
|
||||
),
|
||||
];
|
||||
|
||||
/// Pas d'ombre
|
||||
static const List<BoxShadow> none = [];
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/// Export de tous les tokens de design
|
||||
/// Facilite l'importation des tokens dans l'application
|
||||
library tokens;
|
||||
|
||||
// Tokens de couleur
|
||||
export 'color_tokens.dart';
|
||||
|
||||
// Tokens de typographie
|
||||
export 'typography_tokens.dart';
|
||||
|
||||
// Tokens d'espacement
|
||||
export 'spacing_tokens.dart';
|
||||
|
||||
// Tokens de rayon
|
||||
export 'radius_tokens.dart';
|
||||
@@ -0,0 +1,58 @@
|
||||
/// UnionFlow Design System - Point d'entrée unique
|
||||
///
|
||||
/// Ce fichier centralise tous les tokens et composants du Design System UnionFlow.
|
||||
/// Importer ce fichier pour accéder à tous les éléments de design.
|
||||
///
|
||||
/// Palette de couleurs: Bleu Roi (#4169E1) + Bleu Pétrole (#2C5F6F)
|
||||
/// Basé sur Material Design 3 et les tendances UI/UX 2024-2025
|
||||
///
|
||||
/// Usage:
|
||||
/// ```dart
|
||||
/// import 'package:unionflow_mobile_apps/core/design_system/unionflow_design_system.dart';
|
||||
///
|
||||
/// // Utiliser les tokens
|
||||
/// Container(
|
||||
/// color: ColorTokens.primary,
|
||||
/// padding: EdgeInsets.all(SpacingTokens.xl),
|
||||
/// child: Text(
|
||||
/// 'UnionFlow',
|
||||
/// style: TypographyTokens.headlineMedium,
|
||||
/// ),
|
||||
/// );
|
||||
/// ```
|
||||
library unionflow_design_system;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// TOKENS - Valeurs de design fondamentales
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Tokens de couleurs (Bleu Roi + Bleu Pétrole)
|
||||
export 'tokens/color_tokens.dart';
|
||||
|
||||
/// Tokens de typographie (Inter, SF Pro Display, JetBrains Mono)
|
||||
export 'tokens/typography_tokens.dart';
|
||||
|
||||
/// Tokens d'espacement (Grille 4px)
|
||||
export 'tokens/spacing_tokens.dart';
|
||||
|
||||
/// Tokens de rayons de bordure
|
||||
export 'tokens/radius_tokens.dart';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// THÈME - Configuration Material Design 3
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Thème sophistiqué (Light + Dark)
|
||||
export 'theme/app_theme_sophisticated.dart';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COMPOSANTS - Widgets réutilisables (à ajouter progressivement)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// TODO: Ajouter les composants au fur et à mesure de leur création
|
||||
// export 'components/buttons/uf_buttons.dart';
|
||||
// export 'components/cards/uf_cards.dart';
|
||||
// export 'components/inputs/uf_inputs.dart';
|
||||
// export 'components/navigation/uf_navigation.dart';
|
||||
// export 'components/feedback/uf_feedback.dart';
|
||||
|
||||
@@ -1,349 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class AppTheme {
|
||||
// Couleurs principales UnionFlow
|
||||
static const Color primaryColor = Color(0xFF2196F3);
|
||||
static const Color primaryLight = Color(0xFF64B5F6);
|
||||
static const Color primaryDark = Color(0xFF1976D2);
|
||||
|
||||
static const Color secondaryColor = Color(0xFF4CAF50);
|
||||
static const Color secondaryLight = Color(0xFF81C784);
|
||||
static const Color secondaryDark = Color(0xFF388E3C);
|
||||
|
||||
static const Color accentColor = Color(0xFFFF9800);
|
||||
static const Color errorColor = Color(0xFFE53935);
|
||||
static const Color warningColor = Color(0xFFFFC107);
|
||||
static const Color successColor = Color(0xFF4CAF50);
|
||||
static const Color infoColor = Color(0xFF2196F3);
|
||||
|
||||
// Couleurs neutres
|
||||
static const Color backgroundLight = Color(0xFFFAFAFA);
|
||||
static const Color backgroundDark = Color(0xFF121212);
|
||||
static const Color surfaceLight = Color(0xFFFFFFFF);
|
||||
static const Color surfaceDark = Color(0xFF1E1E1E);
|
||||
|
||||
static const Color textPrimary = Color(0xFF212121);
|
||||
static const Color textSecondary = Color(0xFF757575);
|
||||
static const Color textHint = Color(0xFFBDBDBD);
|
||||
static const Color textWhite = Color(0xFFFFFFFF);
|
||||
|
||||
// Bordures et dividers
|
||||
static const Color borderColor = Color(0xFFE0E0E0);
|
||||
static const Color borderLight = Color(0xFFF5F5F5);
|
||||
static const Color dividerColor = Color(0xFFBDBDBD);
|
||||
|
||||
// Couleurs Material 3 supplémentaires pour les composants unifiés
|
||||
static const Color outline = Color(0xFFE0E0E0);
|
||||
static const Color surfaceVariant = Color(0xFFF5F5F5);
|
||||
static const Color onSurfaceVariant = Color(0xFF757575);
|
||||
|
||||
// Tokens de design unifiés
|
||||
static const double borderRadiusSmall = 8.0;
|
||||
static const double borderRadiusMedium = 12.0;
|
||||
static const double borderRadiusLarge = 16.0;
|
||||
static const double borderRadiusXLarge = 20.0;
|
||||
|
||||
static const double spacingXSmall = 4.0;
|
||||
static const double spacingSmall = 8.0;
|
||||
static const double spacingMedium = 16.0;
|
||||
static const double spacingLarge = 24.0;
|
||||
static const double spacingXLarge = 32.0;
|
||||
|
||||
static const double elevationSmall = 1.0;
|
||||
static const double elevationMedium = 2.0;
|
||||
static const double elevationLarge = 4.0;
|
||||
static const double elevationXLarge = 8.0;
|
||||
|
||||
// Styles de texte unifiés
|
||||
static const TextStyle headlineSmall = TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle titleMedium = TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle bodyMedium = TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle bodySmall = TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: textSecondary,
|
||||
);
|
||||
|
||||
static const TextStyle titleSmall = TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle bodyLarge = TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: textPrimary,
|
||||
);
|
||||
|
||||
// Thème clair
|
||||
static ThemeData get lightTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
primarySwatch: _createMaterialColor(primaryColor),
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: primaryColor,
|
||||
onPrimary: textWhite,
|
||||
secondary: secondaryColor,
|
||||
onSecondary: textWhite,
|
||||
error: errorColor,
|
||||
onError: textWhite,
|
||||
surface: surfaceLight,
|
||||
onSurface: textPrimary,
|
||||
),
|
||||
|
||||
// AppBar
|
||||
appBarTheme: const AppBarTheme(
|
||||
elevation: 0,
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: textWhite,
|
||||
centerTitle: true,
|
||||
systemOverlayStyle: SystemUiOverlayStyle.light,
|
||||
titleTextStyle: TextStyle(
|
||||
color: textWhite,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
|
||||
// Cards
|
||||
cardTheme: CardTheme(
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withOpacity(0.1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
color: surfaceLight,
|
||||
),
|
||||
|
||||
// Boutons
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: textWhite,
|
||||
elevation: 2,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: primaryColor,
|
||||
side: const BorderSide(color: primaryColor, width: 2),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: primaryColor,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Champs de saisie
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: surfaceLight,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: borderColor),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: borderColor),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: primaryColor, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: errorColor),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
hintStyle: const TextStyle(color: textHint),
|
||||
),
|
||||
|
||||
// Navigation bottom
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
backgroundColor: surfaceLight,
|
||||
selectedItemColor: primaryColor,
|
||||
unselectedItemColor: textSecondary,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
elevation: 8,
|
||||
),
|
||||
|
||||
// Chip
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: primaryLight.withOpacity(0.1),
|
||||
selectedColor: primaryColor,
|
||||
labelStyle: const TextStyle(color: textPrimary),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
|
||||
// Divider
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: dividerColor,
|
||||
thickness: 1,
|
||||
),
|
||||
|
||||
// Typography
|
||||
textTheme: _textTheme,
|
||||
);
|
||||
}
|
||||
|
||||
// Thème sombre (pour plus tard)
|
||||
static ThemeData get darkTheme {
|
||||
return lightTheme.copyWith(
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: backgroundDark,
|
||||
// TODO: Implémenter le thème sombre complet
|
||||
);
|
||||
}
|
||||
|
||||
// Création d'un MaterialColor à partir d'une Color
|
||||
static MaterialColor _createMaterialColor(Color color) {
|
||||
List strengths = <double>[.05];
|
||||
Map<int, Color> swatch = <int, Color>{};
|
||||
final int r = color.red, g = color.green, b = color.blue;
|
||||
|
||||
for (int i = 1; i < 10; i++) {
|
||||
strengths.add(0.1 * i);
|
||||
}
|
||||
for (var strength in strengths) {
|
||||
final double ds = 0.5 - strength;
|
||||
swatch[(strength * 1000).round()] = Color.fromRGBO(
|
||||
r + ((ds < 0 ? r : (255 - r)) * ds).round(),
|
||||
g + ((ds < 0 ? g : (255 - g)) * ds).round(),
|
||||
b + ((ds < 0 ? b : (255 - b)) * ds).round(),
|
||||
1,
|
||||
);
|
||||
}
|
||||
return MaterialColor(color.value, swatch);
|
||||
}
|
||||
|
||||
// Typographie
|
||||
static const TextTheme _textTheme = TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textPrimary,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textPrimary,
|
||||
),
|
||||
displaySmall: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textPrimary,
|
||||
),
|
||||
headlineLarge: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textPrimary,
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textPrimary,
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textPrimary,
|
||||
),
|
||||
titleLarge: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textPrimary,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textPrimary,
|
||||
),
|
||||
titleSmall: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textPrimary,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: textPrimary,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: textPrimary,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: textSecondary,
|
||||
),
|
||||
labelLarge: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textPrimary,
|
||||
),
|
||||
labelMedium: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textSecondary,
|
||||
),
|
||||
labelSmall: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textHint,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Extensions pour faciliter l'utilisation
|
||||
extension ThemeExtension on BuildContext {
|
||||
ThemeData get theme => Theme.of(this);
|
||||
ColorScheme get colors => Theme.of(this).colorScheme;
|
||||
TextTheme get textTheme => Theme.of(this).textTheme;
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'app_theme.dart';
|
||||
|
||||
/// Design System UnionFlow basé sur le nombre d'or et Material Design 3
|
||||
class DesignSystem {
|
||||
// === NOMBRE D'OR ET PROPORTIONS ===
|
||||
static const double goldenRatio = 1.618;
|
||||
static const double inverseGoldenRatio = 0.618;
|
||||
|
||||
// === ESPACEMENTS BASÉS SUR LE NOMBRE D'OR ===
|
||||
static const double baseUnit = 8.0;
|
||||
|
||||
// Espacements principaux (progression géométrique basée sur le nombre d'or)
|
||||
static const double spacing2xs = baseUnit * 0.5; // 4px
|
||||
static const double spacingXs = baseUnit; // 8px
|
||||
static const double spacingSm = baseUnit * 1.5; // 12px
|
||||
static const double spacingMd = baseUnit * 2; // 16px
|
||||
static const double spacingLg = baseUnit * 3; // 24px
|
||||
static const double spacingXl = baseUnit * 4; // 32px
|
||||
static const double spacing2xl = baseUnit * 6; // 48px
|
||||
static const double spacing3xl = baseUnit * 8; // 64px
|
||||
|
||||
// Espacements spéciaux basés sur le nombre d'or
|
||||
static const double spacingGolden = spacingMd * goldenRatio; // ~26px
|
||||
static const double spacingGoldenLarge = spacingLg * goldenRatio; // ~39px
|
||||
|
||||
// === RAYONS DE BORDURE ===
|
||||
static const double radiusXs = 4.0;
|
||||
static const double radiusSm = 8.0;
|
||||
static const double radiusMd = 12.0;
|
||||
static const double radiusLg = 16.0;
|
||||
static const double radiusXl = 20.0;
|
||||
static const double radius2xl = 24.0;
|
||||
|
||||
// === ÉLÉVATIONS ET OMBRES ===
|
||||
static const double elevationCard = 2.0;
|
||||
static const double elevationModal = 8.0;
|
||||
static const double elevationAppBar = 0.0;
|
||||
|
||||
// Ombres personnalisées
|
||||
static List<BoxShadow> get shadowCard => [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.04),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.02),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
static List<BoxShadow> get shadowCardHover => [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.04),
|
||||
blurRadius: 32,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
];
|
||||
|
||||
static List<BoxShadow> get shadowModal => [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.12),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 48,
|
||||
offset: const Offset(0, 16),
|
||||
),
|
||||
];
|
||||
|
||||
// === TYPOGRAPHIE AVANCÉE ===
|
||||
static const TextStyle displayLarge = TextStyle(
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.5,
|
||||
height: 1.2,
|
||||
color: AppTheme.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle displayMedium = TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.25,
|
||||
height: 1.25,
|
||||
color: AppTheme.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle headlineLarge = TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0,
|
||||
height: 1.3,
|
||||
color: AppTheme.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle headlineMedium = TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0,
|
||||
height: 1.33,
|
||||
color: AppTheme.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle titleLarge = TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0,
|
||||
height: 1.4,
|
||||
color: AppTheme.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle titleMedium = TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.5,
|
||||
color: AppTheme.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle bodyLarge = TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.15,
|
||||
height: 1.5,
|
||||
color: AppTheme.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle bodyMedium = TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.25,
|
||||
height: 1.43,
|
||||
color: AppTheme.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle labelLarge = TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.43,
|
||||
color: AppTheme.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle labelMedium = TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.5,
|
||||
height: 1.33,
|
||||
color: AppTheme.textSecondary,
|
||||
);
|
||||
|
||||
static const TextStyle labelSmall = TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.5,
|
||||
height: 1.2,
|
||||
color: AppTheme.textHint,
|
||||
);
|
||||
|
||||
// === COULEURS ÉTENDUES ===
|
||||
// Palette de couleurs pour les graphiques (harmonieuse et accessible)
|
||||
static const List<Color> chartColors = [
|
||||
Color(0xFF2196F3), // Bleu principal
|
||||
Color(0xFF4CAF50), // Vert
|
||||
Color(0xFFFF9800), // Orange
|
||||
Color(0xFF9C27B0), // Violet
|
||||
Color(0xFFF44336), // Rouge
|
||||
Color(0xFF00BCD4), // Cyan
|
||||
Color(0xFFFFEB3B), // Jaune
|
||||
Color(0xFF795548), // Marron
|
||||
Color(0xFF607D8B), // Bleu gris
|
||||
Color(0xFFE91E63), // Rose
|
||||
];
|
||||
|
||||
// Couleurs de gradient
|
||||
static const LinearGradient primaryGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
AppTheme.primaryColor,
|
||||
AppTheme.primaryLight,
|
||||
],
|
||||
);
|
||||
|
||||
static const LinearGradient successGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
AppTheme.successColor,
|
||||
AppTheme.secondaryLight,
|
||||
],
|
||||
);
|
||||
|
||||
static const LinearGradient warningGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
AppTheme.warningColor,
|
||||
Color(0xFFFFB74D),
|
||||
],
|
||||
);
|
||||
|
||||
static const LinearGradient errorGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
AppTheme.errorColor,
|
||||
Color(0xFFEF5350),
|
||||
],
|
||||
);
|
||||
|
||||
// === ANIMATIONS ET TRANSITIONS ===
|
||||
static const Duration animationFast = Duration(milliseconds: 150);
|
||||
static const Duration animationMedium = Duration(milliseconds: 300);
|
||||
static const Duration animationSlow = Duration(milliseconds: 500);
|
||||
|
||||
static const Curve animationCurve = Curves.easeInOutCubic;
|
||||
static const Curve animationCurveEnter = Curves.easeOut;
|
||||
static const Curve animationCurveExit = Curves.easeIn;
|
||||
|
||||
// === BREAKPOINTS RESPONSIVE ===
|
||||
static const double breakpointMobile = 480;
|
||||
static const double breakpointTablet = 768;
|
||||
static const double breakpointDesktop = 1024;
|
||||
|
||||
// === UTILITAIRES ===
|
||||
static bool isMobile(BuildContext context) {
|
||||
return MediaQuery.of(context).size.width < breakpointMobile;
|
||||
}
|
||||
|
||||
static bool isTablet(BuildContext context) {
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
return width >= breakpointMobile && width < breakpointDesktop;
|
||||
}
|
||||
|
||||
static bool isDesktop(BuildContext context) {
|
||||
return MediaQuery.of(context).size.width >= breakpointDesktop;
|
||||
}
|
||||
|
||||
// Calcul de dimensions basées sur le nombre d'or
|
||||
static double goldenWidth(double height) => height * goldenRatio;
|
||||
static double goldenHeight(double width) => width * inverseGoldenRatio;
|
||||
|
||||
// Espacement adaptatif basé sur la taille d'écran
|
||||
static double adaptiveSpacing(BuildContext context, {
|
||||
double mobile = spacingMd,
|
||||
double tablet = spacingLg,
|
||||
double desktop = spacingXl,
|
||||
}) {
|
||||
if (isMobile(context)) return mobile;
|
||||
if (isTablet(context)) return tablet;
|
||||
return desktop;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user