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