gitignore propre

This commit is contained in:
dahoud
2025-11-09 16:31:19 +00:00
parent 8cec643361
commit 990ee549e6
164 changed files with 2769 additions and 22721 deletions

View File

@@ -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

View File

@@ -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,
),
],
),
),
);
}
}

View File

@@ -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,
),
],
),
),
);
}
}

View File

@@ -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,
}

View File

@@ -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,
],
),
);
}
}

View File

@@ -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,
),
],
),
);
}
}

View File

@@ -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,
),
),
],
],
),
),
),
);
}
}

View File

@@ -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';

View File

@@ -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(),
),
),
),
],
),
);
}
}

View File

@@ -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,
),
],
),
);
}
}

View File

@@ -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),
);
}

View File

@@ -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,
);
}
}

View File

@@ -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,
});
}

View File

@@ -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,
);
}

View File

@@ -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 = [];
}

View File

@@ -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';

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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;
}
}