Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts). Signed-off-by: lions dev Team
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Background avec motifs géométriques africains subtils
|
||||
class AfricanPatternBackground extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Color? patternColor;
|
||||
final double opacity;
|
||||
|
||||
const AfricanPatternBackground({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.patternColor,
|
||||
this.opacity = 0.03,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
// Background avec motifs
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: CustomPaint(
|
||||
painter: AfricanPatternPainter(
|
||||
color: (patternColor ?? UnionFlowColors.unionGreen).withOpacity(opacity),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Contenu
|
||||
child,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Painter pour dessiner les motifs africains
|
||||
class AfricanPatternPainter extends CustomPainter {
|
||||
final Color color;
|
||||
|
||||
AfricanPatternPainter({required this.color});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2;
|
||||
|
||||
final fillPaint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
// Espacement entre les motifs
|
||||
const double spacing = 80.0;
|
||||
const double patternSize = 40.0;
|
||||
|
||||
// Dessiner la grille de motifs
|
||||
for (double y = 0; y < size.height + spacing; y += spacing) {
|
||||
for (double x = 0; x < size.width + spacing; x += spacing) {
|
||||
final offset = Offset(x, y);
|
||||
|
||||
// Alterner entre différents motifs
|
||||
final patternType = ((x ~/ spacing) + (y ~/ spacing)) % 3;
|
||||
|
||||
switch (patternType) {
|
||||
case 0:
|
||||
_drawDiamondPattern(canvas, offset, patternSize, paint);
|
||||
break;
|
||||
case 1:
|
||||
_drawTrianglePattern(canvas, offset, patternSize, fillPaint);
|
||||
break;
|
||||
case 2:
|
||||
_drawCirclePattern(canvas, offset, patternSize, paint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _drawDiamondPattern(Canvas canvas, Offset offset, double size, Paint paint) {
|
||||
final path = Path()
|
||||
..moveTo(offset.dx, offset.dy - size / 2)
|
||||
..lineTo(offset.dx + size / 2, offset.dy)
|
||||
..lineTo(offset.dx, offset.dy + size / 2)
|
||||
..lineTo(offset.dx - size / 2, offset.dy)
|
||||
..close();
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
void _drawTrianglePattern(Canvas canvas, Offset offset, double size, Paint paint) {
|
||||
final path = Path()
|
||||
..moveTo(offset.dx, offset.dy - size / 3)
|
||||
..lineTo(offset.dx + size / 3, offset.dy + size / 3)
|
||||
..lineTo(offset.dx - size / 3, offset.dy + size / 3)
|
||||
..close();
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
void _drawCirclePattern(Canvas canvas, Offset offset, double size, Paint paint) {
|
||||
canvas.drawCircle(offset, size / 4, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
60
lib/shared/design_system/components/animated_fade_in.dart
Normal file
60
lib/shared/design_system/components/animated_fade_in.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Widget avec animation de fade-in automatique
|
||||
class AnimatedFadeIn extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Duration duration;
|
||||
final Duration delay;
|
||||
final Curve curve;
|
||||
|
||||
const AnimatedFadeIn({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.duration = const Duration(milliseconds: 600),
|
||||
this.delay = Duration.zero,
|
||||
this.curve = Curves.easeOut,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AnimatedFadeIn> createState() => _AnimatedFadeInState();
|
||||
}
|
||||
|
||||
class _AnimatedFadeInState extends State<AnimatedFadeIn>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: widget.duration,
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_animation = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: widget.curve,
|
||||
);
|
||||
|
||||
Future.delayed(widget.delay, () {
|
||||
if (mounted) {
|
||||
_controller.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FadeTransition(
|
||||
opacity: _animation,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
74
lib/shared/design_system/components/animated_slide_in.dart
Normal file
74
lib/shared/design_system/components/animated_slide_in.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Widget avec animation de slide-in automatique
|
||||
class AnimatedSlideIn extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Duration duration;
|
||||
final Duration delay;
|
||||
final Offset begin;
|
||||
final Curve curve;
|
||||
|
||||
const AnimatedSlideIn({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.duration = const Duration(milliseconds: 600),
|
||||
this.delay = Duration.zero,
|
||||
this.begin = const Offset(0, 0.3),
|
||||
this.curve = Curves.easeOut,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AnimatedSlideIn> createState() => _AnimatedSlideInState();
|
||||
}
|
||||
|
||||
class _AnimatedSlideInState extends State<AnimatedSlideIn>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
late Animation<double> _fadeAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: widget.duration,
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: widget.begin,
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: widget.curve,
|
||||
));
|
||||
|
||||
_fadeAnimation = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: widget.curve,
|
||||
);
|
||||
|
||||
Future.delayed(widget.delay, () {
|
||||
if (mounted) {
|
||||
_controller.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/// 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 '../../unionflow_design_system.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;
|
||||
|
||||
/// Couleur de fond personnalisée (optionnel)
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Couleur du texte/icône personnalisée (optionnel)
|
||||
final Color? textColor;
|
||||
|
||||
const UFPrimaryButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.onPressed,
|
||||
this.isLoading = false,
|
||||
this.icon,
|
||||
this.isFullWidth = false,
|
||||
this.height,
|
||||
this.backgroundColor,
|
||||
this.textColor,
|
||||
});
|
||||
|
||||
@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: backgroundColor ?? AppColors.primaryGreen,
|
||||
foregroundColor: textColor ?? Colors.white,
|
||||
disabledBackgroundColor: (backgroundColor ?? AppColors.primaryGreen).withOpacity(0.5),
|
||||
disabledForegroundColor: (textColor ?? Colors.white).withOpacity(0.7),
|
||||
elevation: SpacingTokens.elevationSm,
|
||||
shadowColor: AppColors.darkBorder.withOpacity(0.1),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.buttonPaddingHorizontal,
|
||||
vertical: SpacingTokens.buttonPaddingVertical,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(icon, size: 20),
|
||||
const SizedBox(width: SpacingTokens.md),
|
||||
],
|
||||
Text(
|
||||
label,
|
||||
style: AppTypography.actionText,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/// 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 '../../unionflow_design_system.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: AppColors.brandGreen,
|
||||
foregroundColor: Colors.white,
|
||||
disabledBackgroundColor: AppColors.brandGreen.withOpacity(0.5),
|
||||
disabledForegroundColor: Colors.white.withOpacity(0.7),
|
||||
elevation: SpacingTokens.elevationSm,
|
||||
shadowColor: AppColors.darkBorder.withOpacity(0.1),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.buttonPaddingHorizontal,
|
||||
vertical: SpacingTokens.buttonPaddingVertical,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(icon, size: 20),
|
||||
const SizedBox(width: SpacingTokens.md),
|
||||
],
|
||||
Text(
|
||||
label,
|
||||
style: AppTypography.actionText,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
158
lib/shared/design_system/components/cards/uf_card.dart
Normal file
158
lib/shared/design_system/components/cards/uf_card.dart
Normal file
@@ -0,0 +1,158 @@
|
||||
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 ?? const 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 ?? AppColors.lightSurface,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
boxShadow: elevation != null
|
||||
? [
|
||||
BoxShadow(
|
||||
color: AppColors.darkBorder.withOpacity(0.1),
|
||||
blurRadius: elevation!,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
]
|
||||
: ShadowTokens.sm,
|
||||
);
|
||||
|
||||
case UFCardStyle.outlined:
|
||||
return BoxDecoration(
|
||||
color: color ?? AppColors.lightSurface,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
border: Border.all(
|
||||
color: borderColor ?? AppColors.lightBorder,
|
||||
width: borderWidth ?? 1.0,
|
||||
),
|
||||
);
|
||||
|
||||
case UFCardStyle.filled:
|
||||
return BoxDecoration(
|
||||
color: color ?? AppColors.lightSurface,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Styles de Card disponibles
|
||||
enum UFCardStyle {
|
||||
/// Card avec ombre
|
||||
elevated,
|
||||
|
||||
/// Card avec bordure
|
||||
outlined,
|
||||
|
||||
/// Card avec fond coloré
|
||||
filled,
|
||||
}
|
||||
|
||||
89
lib/shared/design_system/components/cards/uf_info_card.dart
Normal file
89
lib/shared/design_system/components/cards/uf_info_card.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
/// 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 '../../unionflow_design_system.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 ?? AppColors.primaryGreen;
|
||||
final effectivePadding = padding ?? const EdgeInsets.all(SpacingTokens.xl);
|
||||
|
||||
return Container(
|
||||
padding: effectivePadding,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
boxShadow: ShadowTokens.sm,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header avec titre et trailing
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, color: effectiveIconColor, size: 20),
|
||||
const SizedBox(width: SpacingTokens.md),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.textPrimaryLight,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
const SizedBox(height: SpacingTokens.xl),
|
||||
// Contenu
|
||||
child,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/// 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 '../../unionflow_design_system.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: const 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),
|
||||
const SizedBox(height: SpacingTokens.sm),
|
||||
Text(
|
||||
value,
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
fontSize: 9,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
141
lib/shared/design_system/components/cards/uf_stat_card.dart
Normal file
141
lib/shared/design_system/components/cards/uf_stat_card.dart
Normal file
@@ -0,0 +1,141 @@
|
||||
/// 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 '../../unionflow_design_system.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 ?? AppColors.primaryGreen;
|
||||
final effectiveIconBgColor = iconBackgroundColor ??
|
||||
effectiveIconColor.withOpacity(0.1);
|
||||
|
||||
return Card(
|
||||
elevation: SpacingTokens.elevationSm,
|
||||
shadowColor: AppColors.darkBorder.withOpacity(0.1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
child: Padding(
|
||||
padding: const 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: const 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)
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: AppColors.textSecondaryLight,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: SpacingTokens.lg),
|
||||
|
||||
// Titre
|
||||
Text(
|
||||
title,
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: SpacingTokens.sm),
|
||||
|
||||
// Valeur
|
||||
Text(
|
||||
value,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.textPrimaryLight,
|
||||
),
|
||||
),
|
||||
|
||||
// Sous-titre optionnel
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: SpacingTokens.sm),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
43
lib/shared/design_system/components/components.dart
Normal file
43
lib/shared/design_system/components/components.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
/// 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 & CONTAINERS
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export 'cards/uf_card.dart';
|
||||
export 'cards/uf_stat_card.dart';
|
||||
export 'cards/uf_info_card.dart';
|
||||
export 'cards/uf_metric_card.dart';
|
||||
export 'uf_container.dart';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// INPUTS
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export 'inputs/uf_switch_tile.dart';
|
||||
export 'inputs/uf_dropdown_tile.dart';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// HEADERS & APPBAR
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export 'uf_header.dart';
|
||||
export 'uf_page_header.dart';
|
||||
export 'uf_app_bar.dart';
|
||||
|
||||
// Composants supplémentaires à exporter quand créés :
|
||||
// export 'buttons/uf_outline_button.dart';
|
||||
// export 'buttons/uf_text_button.dart';
|
||||
// export 'cards/uf_event_card.dart';
|
||||
// export 'inputs/uf_text_field.dart';
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/// 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 '../../unionflow_design_system.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 ?? AppColors.lightSurface;
|
||||
final effectiveItemBuilder = itemBuilder ?? (item) => item.toString();
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: SpacingTokens.lg),
|
||||
padding: const EdgeInsets.all(SpacingTokens.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: effectiveBgColor,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: AppTypography.bodyTextSmall.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimaryLight,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
border: Border.all(color: AppColors.lightBorder),
|
||||
),
|
||||
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,88 @@
|
||||
/// 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 '../../unionflow_design_system.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 ?? AppColors.lightSurface;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: SpacingTokens.lg),
|
||||
padding: const 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: AppTypography.bodyTextSmall.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimaryLight,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: AppColors.primaryGreen,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
103
lib/shared/design_system/components/uf_app_bar.dart
Normal file
103
lib/shared/design_system/components/uf_app_bar.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
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.
|
||||
///
|
||||
/// Si [mergeLeadingWithTitle] est true et que la route peut être quittée,
|
||||
/// le bouton retour et le titre sont fusionnés en une seule ligne (retour
|
||||
/// toujours visible avec la même couleur que le titre).
|
||||
class UFAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final String title;
|
||||
final List<Widget>? actions;
|
||||
final Widget? leading;
|
||||
final bool automaticallyImplyLeading;
|
||||
/// Fusionne le bouton retour et le titre en une seule zone (retour visible, même couleur que le titre).
|
||||
final bool mergeLeadingWithTitle;
|
||||
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.mergeLeadingWithTitle = false,
|
||||
this.bottom,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.elevation = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final canPop = ModalRoute.of(context)?.canPop ?? false;
|
||||
final fg = foregroundColor ?? Colors.white;
|
||||
final useMergedTitle = mergeLeadingWithTitle && canPop;
|
||||
|
||||
final isTransparent = backgroundColor == Colors.transparent ||
|
||||
(backgroundColor != null && backgroundColor!.opacity < 0.1);
|
||||
|
||||
return AppBar(
|
||||
title: useMergedTitle
|
||||
? Row(
|
||||
children: [
|
||||
Material(
|
||||
color: isTransparent ? Colors.black26 : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
color: fg,
|
||||
tooltip: 'Retour',
|
||||
style: IconButton.styleFrom(
|
||||
foregroundColor: fg,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: fg,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(title),
|
||||
backgroundColor: backgroundColor ?? AppColors.primaryGreen,
|
||||
foregroundColor: fg,
|
||||
elevation: elevation,
|
||||
leading: useMergedTitle ? null : leading,
|
||||
automaticallyImplyLeading: useMergedTitle ? false : automaticallyImplyLeading,
|
||||
actions: actions,
|
||||
bottom: bottom,
|
||||
systemOverlayStyle: const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
statusBarBrightness: Brightness.dark,
|
||||
),
|
||||
centerTitle: false,
|
||||
titleTextStyle: AppTypography.headerSmall.copyWith(
|
||||
color: fg,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(
|
||||
kToolbarHeight + (bottom?.preferredSize.height ?? 0.0),
|
||||
);
|
||||
}
|
||||
|
||||
2
lib/shared/design_system/components/uf_buttons.dart
Normal file
2
lib/shared/design_system/components/uf_buttons.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
export 'buttons/uf_primary_button.dart';
|
||||
export 'buttons/uf_secondary_button.dart';
|
||||
138
lib/shared/design_system/components/uf_container.dart
Normal file
138
lib/shared/design_system/components/uf_container.dart
Normal file
@@ -0,0 +1,138 @@
|
||||
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 = ShadowTokens.sm;
|
||||
|
||||
/// 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 ?? AppColors.lightSurface) : null,
|
||||
gradient: gradient,
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
border: border,
|
||||
boxShadow: boxShadow,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
127
lib/shared/design_system/components/uf_header.dart
Normal file
127
lib/shared/design_system/components/uf_header.dart
Normal file
@@ -0,0 +1,127 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../unionflow_design_system.dart';
|
||||
|
||||
/// Header harmonisé UnionFlow
|
||||
///
|
||||
/// Composant header standardisé pour toutes les pages de l'application.
|
||||
/// Garantit la cohérence visuelle et l'expérience utilisateur.
|
||||
class UFHeader extends StatelessWidget {
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final IconData icon;
|
||||
final List<Widget>? actions;
|
||||
final VoidCallback? onNotificationTap;
|
||||
final VoidCallback? onSettingsTap;
|
||||
final bool showActions;
|
||||
|
||||
const UFHeader({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
required this.icon,
|
||||
this.actions,
|
||||
this.onNotificationTap,
|
||||
this.onSettingsTap,
|
||||
this.showActions = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.xl),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [AppColors.primaryGreen, AppColors.brandGreenLight],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
boxShadow: ShadowTokens.primary,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icône et contenu principal
|
||||
Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: SpacingTokens.lg),
|
||||
|
||||
// Titre et sous-titre
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: SpacingTokens.xs),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Actions
|
||||
if (showActions) _buildActions(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActions() {
|
||||
if (actions != null) {
|
||||
return Row(children: actions!);
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (onNotificationTap != null)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: onNotificationTap,
|
||||
icon: const Icon(
|
||||
Icons.notifications_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (onNotificationTap != null && onSettingsTap != null)
|
||||
const SizedBox(width: SpacingTokens.sm),
|
||||
if (onSettingsTap != null)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: onSettingsTap,
|
||||
icon: const Icon(
|
||||
Icons.settings_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
237
lib/shared/design_system/components/uf_page_header.dart
Normal file
237
lib/shared/design_system/components/uf_page_header.dart
Normal file
@@ -0,0 +1,237 @@
|
||||
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: () => Navigator.pop(context)),
|
||||
/// ],
|
||||
/// )
|
||||
/// ```
|
||||
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 ?? AppColors.primaryGreen;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.lg,
|
||||
vertical: SpacingTokens.md,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icône
|
||||
Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: effectiveIconColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: effectiveIconColor,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: SpacingTokens.md),
|
||||
|
||||
// Titre
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.textPrimaryLight,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Actions
|
||||
if (actions != null) ...actions!,
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Divider optionnel
|
||||
if (showDivider)
|
||||
const Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: AppColors.lightBorder,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Header de page avec statistiques
|
||||
///
|
||||
/// Header compact avec des métriques KPI intégrées.
|
||||
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 ?? AppColors.primaryGreen;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Titre et actions
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
SpacingTokens.lg,
|
||||
SpacingTokens.md,
|
||||
SpacingTokens.lg,
|
||||
SpacingTokens.sm,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icône
|
||||
Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: effectiveIconColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: effectiveIconColor,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: SpacingTokens.md),
|
||||
|
||||
// Titre
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.textPrimaryLight,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Actions
|
||||
if (actions != null) ...actions!,
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Statistiques
|
||||
Padding(
|
||||
padding: const 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
|
||||
const Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: AppColors.lightBorder,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatItem(UFHeaderStat stat) {
|
||||
final effectiveColor = stat.color ?? AppColors.primaryGreen;
|
||||
return UFContainer.rounded(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.md,
|
||||
vertical: SpacingTokens.sm,
|
||||
),
|
||||
color: effectiveColor.withOpacity(0.05),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
stat.value,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: effectiveColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: SpacingTokens.xs),
|
||||
Text(
|
||||
stat.label,
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
),
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
81
lib/shared/design_system/components/union_action_button.dart
Normal file
81
lib/shared/design_system/components/union_action_button.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Bouton d'action rapide UnionFlow
|
||||
class UnionActionButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final VoidCallback onTap;
|
||||
final Color? backgroundColor;
|
||||
final Color? iconColor;
|
||||
|
||||
const UnionActionButton({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.onTap,
|
||||
this.backgroundColor,
|
||||
this.iconColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ?? UnionFlowColors.unionGreenPale,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: (backgroundColor ?? UnionFlowColors.unionGreenPale)
|
||||
.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 28,
|
||||
color: iconColor ?? UnionFlowColors.unionGreen,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Grid d'actions rapides
|
||||
class UnionActionGrid extends StatelessWidget {
|
||||
final List<UnionActionButton> actions;
|
||||
|
||||
const UnionActionGrid({
|
||||
super.key,
|
||||
required this.actions,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
for (int i = 0; i < actions.length; i++) ...[
|
||||
Expanded(child: actions[i]),
|
||||
if (i < actions.length - 1) const SizedBox(width: 12),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
98
lib/shared/design_system/components/union_balance_card.dart
Normal file
98
lib/shared/design_system/components/union_balance_card.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Card de balance UnionFlow - Affichage élégant du solde principal
|
||||
class UnionBalanceCard extends StatelessWidget {
|
||||
final String label;
|
||||
final String amount;
|
||||
final String? trend;
|
||||
final bool? isTrendPositive;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const UnionBalanceCard({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.amount,
|
||||
this.trend,
|
||||
this.isTrendPositive,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: UnionFlowColors.softShadow,
|
||||
// Bordure dorée subtile en haut
|
||||
border: const Border(
|
||||
top: BorderSide(
|
||||
color: UnionFlowColors.gold,
|
||||
width: 3,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Label
|
||||
Text(
|
||||
label.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
letterSpacing: 0.8,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Montant principal
|
||||
Text(
|
||||
amount,
|
||||
style: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: UnionFlowColors.unionGreen,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
|
||||
// Trend (optionnel)
|
||||
if (trend != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isTrendPositive == true
|
||||
? Icons.trending_up
|
||||
: Icons.trending_down,
|
||||
size: 16,
|
||||
color: isTrendPositive == true
|
||||
? UnionFlowColors.success
|
||||
: UnionFlowColors.error,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
trend!,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isTrendPositive == true
|
||||
? UnionFlowColors.success
|
||||
: UnionFlowColors.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
168
lib/shared/design_system/components/union_export_button.dart
Normal file
168
lib/shared/design_system/components/union_export_button.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Type d'export disponible
|
||||
enum ExportType {
|
||||
pdf('PDF', Icons.picture_as_pdf),
|
||||
excel('Excel', Icons.table_chart),
|
||||
csv('CSV', Icons.description);
|
||||
|
||||
final String label;
|
||||
final IconData icon;
|
||||
const ExportType(this.label, this.icon);
|
||||
}
|
||||
|
||||
/// Bouton d'export avec options
|
||||
class UnionExportButton extends StatelessWidget {
|
||||
final Function(ExportType) onExport;
|
||||
final bool isLoading;
|
||||
|
||||
const UnionExportButton({
|
||||
super.key,
|
||||
required this.onExport,
|
||||
this.isLoading = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<ExportType>(
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
gradient: UnionFlowColors.primaryGradient,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: UnionFlowColors.greenGlowShadow,
|
||||
),
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.download,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
itemBuilder: (context) => ExportType.values.map((type) {
|
||||
return PopupMenuItem<ExportType>(
|
||||
value: type,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
type.icon,
|
||||
size: 20,
|
||||
color: UnionFlowColors.unionGreen,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Exporter en ${type.label}',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onSelected: onExport,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 8,
|
||||
color: UnionFlowColors.surface,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dialog pour confirmer l'export
|
||||
class ExportConfirmDialog extends StatelessWidget {
|
||||
final ExportType exportType;
|
||||
final VoidCallback onConfirm;
|
||||
final String? message;
|
||||
|
||||
const ExportConfirmDialog({
|
||||
super.key,
|
||||
required this.exportType,
|
||||
required this.onConfirm,
|
||||
this.message,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
backgroundColor: UnionFlowColors.surface,
|
||||
title: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.unionGreen.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
exportType.icon,
|
||||
color: UnionFlowColors.unionGreen,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Exporter en ${exportType.label}',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Text(
|
||||
message ?? 'Voulez-vous exporter le rapport au format ${exportType.label}?',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text(
|
||||
'Annuler',
|
||||
style: TextStyle(
|
||||
color: UnionFlowColors.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
onConfirm();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UnionFlowColors.unionGreen,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
child: const Text(
|
||||
'Confirmer',
|
||||
style: TextStyle(fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
65
lib/shared/design_system/components/union_glass_card.dart
Normal file
65
lib/shared/design_system/components/union_glass_card.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Card avec effet glassmorphism
|
||||
class UnionGlassCard extends StatelessWidget {
|
||||
final Widget child;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final EdgeInsetsGeometry? margin;
|
||||
final double? borderRadius;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const UnionGlassCard({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.padding,
|
||||
this.margin,
|
||||
this.borderRadius,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: margin,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(borderRadius ?? 16),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
width: 1.5,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: UnionFlowColors.unionGreen.withOpacity(0.1),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(borderRadius ?? 16),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
padding: padding ?? const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.white.withOpacity(0.2),
|
||||
Colors.white.withOpacity(0.1),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
216
lib/shared/design_system/components/union_line_chart.dart
Normal file
216
lib/shared/design_system/components/union_line_chart.dart
Normal file
@@ -0,0 +1,216 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Graphique en ligne UnionFlow - Pour afficher l'évolution temporelle
|
||||
class UnionLineChart extends StatelessWidget {
|
||||
final List<FlSpot> spots;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final Color? lineColor;
|
||||
final Color? gradientStartColor;
|
||||
final Color? gradientEndColor;
|
||||
|
||||
const UnionLineChart({
|
||||
super.key,
|
||||
required this.spots,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.lineColor,
|
||||
this.gradientStartColor,
|
||||
this.gradientEndColor,
|
||||
});
|
||||
|
||||
/// Calcule maxY de manière sécurisée pour éviter NaN, Infinity ou 0
|
||||
double _calculateSafeMaxY() {
|
||||
if (spots.isEmpty) return 100.0;
|
||||
|
||||
final maxValue = spots.map((e) => e.y).reduce((a, b) => a > b ? a : b);
|
||||
|
||||
// Si maxValue est invalide (NaN, Infinity) ou trop petit
|
||||
if (maxValue.isNaN || maxValue.isInfinite || maxValue <= 0) {
|
||||
return 100.0;
|
||||
}
|
||||
|
||||
return maxValue * 1.2;
|
||||
}
|
||||
|
||||
/// Calcule maxX de manière sécurisée
|
||||
double _calculateSafeMaxX() {
|
||||
if (spots.isEmpty) return 11.0; // 12 mois - 1
|
||||
return spots.length.toDouble() - 1;
|
||||
}
|
||||
|
||||
/// Calcule l'intervalle de grille approprié basé sur maxY
|
||||
double _calculateGridInterval() {
|
||||
final maxY = _calculateSafeMaxY();
|
||||
|
||||
// Calculer un intervalle qui donne environ 4-6 lignes de grille
|
||||
final baseInterval = maxY / 5;
|
||||
|
||||
if (baseInterval == 0) return 20.0; // Fallback si maxY est trop petit
|
||||
|
||||
// Arrondir à un nombre "propre" (puissance de 10)
|
||||
final magnitude = pow(10.0, (log(baseInterval) / log(10.0)).floor()).toDouble();
|
||||
final normalized = baseInterval / magnitude;
|
||||
|
||||
// Arrondir vers le haut au multiple de 1, 2 ou 5 le plus proche
|
||||
double roundedInterval;
|
||||
if (normalized <= 1) {
|
||||
roundedInterval = 1;
|
||||
} else if (normalized <= 2) {
|
||||
roundedInterval = 2;
|
||||
} else if (normalized <= 5) {
|
||||
roundedInterval = 5;
|
||||
} else {
|
||||
roundedInterval = 10;
|
||||
}
|
||||
|
||||
return roundedInterval * magnitude;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveLineColor = lineColor ?? UnionFlowColors.unionGreen;
|
||||
final effectiveGradientStart = gradientStartColor ?? UnionFlowColors.unionGreen.withOpacity(0.3);
|
||||
final effectiveGradientEnd = gradientEndColor ?? UnionFlowColors.unionGreen.withOpacity(0.0);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: UnionFlowColors.softShadow,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Chart
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: _calculateGridInterval(),
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return FlLine(
|
||||
color: UnionFlowColors.border.withOpacity(0.2),
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
interval: 1,
|
||||
getTitlesWidget: (value, meta) {
|
||||
const months = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun', 'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc'];
|
||||
if (value.toInt() >= 0 && value.toInt() < months.length) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
months[value.toInt()],
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: UnionFlowColors.textTertiary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const Text('');
|
||||
},
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 40,
|
||||
getTitlesWidget: (value, meta) {
|
||||
return Text(
|
||||
'${(value / 1000).toStringAsFixed(0)}K',
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
color: UnionFlowColors.textTertiary,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
minX: 0,
|
||||
maxX: _calculateSafeMaxX(),
|
||||
minY: 0,
|
||||
maxY: _calculateSafeMaxY(),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: spots,
|
||||
isCurved: true,
|
||||
color: effectiveLineColor,
|
||||
barWidth: 3,
|
||||
isStrokeCapRound: true,
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 4,
|
||||
color: UnionFlowColors.surface,
|
||||
strokeWidth: 2,
|
||||
strokeColor: effectiveLineColor,
|
||||
);
|
||||
},
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
effectiveGradientStart,
|
||||
effectiveGradientEnd,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Badge de notification avec compteur
|
||||
class UnionNotificationBadge extends StatelessWidget {
|
||||
final int count;
|
||||
final Widget child;
|
||||
final Color? badgeColor;
|
||||
final bool showZero;
|
||||
|
||||
const UnionNotificationBadge({
|
||||
super.key,
|
||||
required this.count,
|
||||
required this.child,
|
||||
this.badgeColor,
|
||||
this.showZero = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final shouldShow = count > 0 || showZero;
|
||||
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
child,
|
||||
if (shouldShow)
|
||||
Positioned(
|
||||
right: -6,
|
||||
top: -6,
|
||||
child: AnimatedScale(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
scale: 1.0,
|
||||
curve: Curves.elasticOut,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: badgeColor ?? UnionFlowColors.error,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: UnionFlowColors.surface,
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (badgeColor ?? UnionFlowColors.error)
|
||||
.withOpacity(0.4),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 18,
|
||||
minHeight: 18,
|
||||
),
|
||||
child: Text(
|
||||
count > 99 ? '99+' : count.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget de notification en temps réel (toast)
|
||||
class UnionNotificationToast extends StatelessWidget {
|
||||
final String title;
|
||||
final String message;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const UnionNotificationToast({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.message,
|
||||
this.icon = Icons.notifications_active,
|
||||
this.color = UnionFlowColors.info,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
static void show(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required String message,
|
||||
IconData icon = Icons.notifications_active,
|
||||
Color color = UnionFlowColors.info,
|
||||
VoidCallback? onTap,
|
||||
}) {
|
||||
final overlay = Overlay.of(context);
|
||||
late OverlayEntry entry;
|
||||
|
||||
entry = OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
top: 60,
|
||||
left: 16,
|
||||
right: 16,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0.0, end: 1.0),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
builder: (context, value, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, -20 * (1 - value)),
|
||||
child: Opacity(
|
||||
opacity: value,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: UnionNotificationToast(
|
||||
title: title,
|
||||
message: message,
|
||||
icon: icon,
|
||||
color: color,
|
||||
onTap: () {
|
||||
entry.remove();
|
||||
onTap?.call();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
overlay.insert(entry);
|
||||
|
||||
// Auto-dismiss après 4 secondes
|
||||
Future.delayed(const Duration(seconds: 4), () {
|
||||
entry.remove();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: UnionFlowColors.mediumShadow,
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: color,
|
||||
width: 4,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 24),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
message,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
size: 18,
|
||||
color: UnionFlowColors.textTertiary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
111
lib/shared/design_system/components/union_period_filter.dart
Normal file
111
lib/shared/design_system/components/union_period_filter.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Filtre de période pour le dashboard
|
||||
enum PeriodFilter {
|
||||
today('Aujourd\'hui'),
|
||||
week('Cette semaine'),
|
||||
month('Ce mois'),
|
||||
quarter('Ce trimestre'),
|
||||
year('Cette année'),
|
||||
custom('Personnalisé');
|
||||
|
||||
final String label;
|
||||
const PeriodFilter(this.label);
|
||||
}
|
||||
|
||||
/// Widget de sélection de période
|
||||
class UnionPeriodFilter extends StatelessWidget {
|
||||
final PeriodFilter selectedPeriod;
|
||||
final Function(PeriodFilter) onPeriodChanged;
|
||||
final bool showCustom;
|
||||
|
||||
const UnionPeriodFilter({
|
||||
super.key,
|
||||
required this.selectedPeriod,
|
||||
required this.onPeriodChanged,
|
||||
this.showCustom = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final periods = showCustom
|
||||
? PeriodFilter.values
|
||||
: PeriodFilter.values.where((p) => p != PeriodFilter.custom).toList();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: UnionFlowColors.softShadow,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.calendar_today,
|
||||
size: 16,
|
||||
color: UnionFlowColors.unionGreen,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Période',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: periods.map((period) {
|
||||
final isSelected = selectedPeriod == period;
|
||||
return GestureDetector(
|
||||
onTap: () => onPeriodChanged(period),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
gradient: isSelected
|
||||
? UnionFlowColors.primaryGradient
|
||||
: null,
|
||||
color: isSelected
|
||||
? null
|
||||
: UnionFlowColors.surfaceVariant,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? UnionFlowColors.unionGreen
|
||||
: UnionFlowColors.border,
|
||||
width: isSelected ? 1.5 : 1,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
period.label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
|
||||
color: isSelected
|
||||
? Colors.white
|
||||
: UnionFlowColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
92
lib/shared/design_system/components/union_pie_chart.dart
Normal file
92
lib/shared/design_system/components/union_pie_chart.dart
Normal file
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Graphique circulaire UnionFlow - Pour afficher des répartitions
|
||||
class UnionPieChart extends StatelessWidget {
|
||||
final List<PieChartSectionData> sections;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final double? centerSpaceRadius;
|
||||
|
||||
const UnionPieChart({
|
||||
super.key,
|
||||
required this.sections,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.centerSpaceRadius,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: UnionFlowColors.softShadow,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Chart
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
sectionsSpace: 2,
|
||||
centerSpaceRadius: centerSpaceRadius ?? 50,
|
||||
sections: sections,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper pour créer des sections de pie chart
|
||||
class UnionPieChartSection {
|
||||
static PieChartSectionData create({
|
||||
required double value,
|
||||
required Color color,
|
||||
required String title,
|
||||
double radius = 50,
|
||||
bool showTitle = true,
|
||||
}) {
|
||||
return PieChartSectionData(
|
||||
color: color,
|
||||
value: value,
|
||||
title: showTitle ? title : '',
|
||||
radius: radius,
|
||||
titleStyle: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
badgeWidget: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
100
lib/shared/design_system/components/union_progress_card.dart
Normal file
100
lib/shared/design_system/components/union_progress_card.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Card de progression UnionFlow avec barre de progrès élégante
|
||||
class UnionProgressCard extends StatelessWidget {
|
||||
final String title;
|
||||
final double progress; // 0.0 à 1.0
|
||||
final String subtitle;
|
||||
final Color? progressColor;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const UnionProgressCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.progress,
|
||||
required this.subtitle,
|
||||
this.progressColor,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final effectiveColor = progressColor ?? UnionFlowColors.gold;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: UnionFlowColors.softShadow,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Progress bar
|
||||
Stack(
|
||||
children: [
|
||||
// Background track
|
||||
Container(
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.border,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
// Progress fill avec gradient
|
||||
FractionallySizedBox(
|
||||
widthFactor: progress.clamp(0.0, 1.0),
|
||||
child: Container(
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
effectiveColor,
|
||||
effectiveColor.withOpacity(0.8),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: effectiveColor.withOpacity(0.3),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Subtitle
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
105
lib/shared/design_system/components/union_stat_widget.dart
Normal file
105
lib/shared/design_system/components/union_stat_widget.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Widget de statistique compacte avec icône et tendance
|
||||
class UnionStatWidget extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final String? trend;
|
||||
final bool? isTrendUp;
|
||||
|
||||
const UnionStatWidget({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
this.trend,
|
||||
this.isTrendUp,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: UnionFlowColors.softShadow,
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: color,
|
||||
width: 4,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Icon
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(icon, size: 20, color: color),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Value
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// Label
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textSecondary,
|
||||
),
|
||||
),
|
||||
|
||||
// Trend
|
||||
if (trend != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isTrendUp == true
|
||||
? Icons.trending_up
|
||||
: Icons.trending_down,
|
||||
size: 14,
|
||||
color: isTrendUp == true
|
||||
? UnionFlowColors.success
|
||||
: UnionFlowColors.error,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
trend!,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isTrendUp == true
|
||||
? UnionFlowColors.success
|
||||
: UnionFlowColors.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
199
lib/shared/design_system/components/union_transaction_tile.dart
Normal file
199
lib/shared/design_system/components/union_transaction_tile.dart
Normal file
@@ -0,0 +1,199 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Tuile de transaction UnionFlow
|
||||
class UnionTransactionTile extends StatelessWidget {
|
||||
final String name;
|
||||
final String amount;
|
||||
final String status;
|
||||
final String? date;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const UnionTransactionTile({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.amount,
|
||||
required this.status,
|
||||
this.date,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
Color _getStatusColor() {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'confirmé':
|
||||
case 'confirmed':
|
||||
return UnionFlowColors.success;
|
||||
case 'en attente':
|
||||
case 'pending':
|
||||
return UnionFlowColors.warning;
|
||||
case 'échoué':
|
||||
case 'failed':
|
||||
return UnionFlowColors.error;
|
||||
default:
|
||||
return UnionFlowColors.textSecondary;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: UnionFlowColors.border,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Avatar avec initiale
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: UnionFlowColors.primaryGradient,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
name.isNotEmpty ? name[0].toUpperCase() : '?',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Nom et date
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
if (date != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
date!,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: UnionFlowColors.textTertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Montant et status
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
amount,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: UnionFlowColors.unionGreen,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor().withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
status,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _getStatusColor(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Liste de transactions dans une card
|
||||
class UnionTransactionCard extends StatelessWidget {
|
||||
final String title;
|
||||
final List<UnionTransactionTile> transactions;
|
||||
final VoidCallback? onSeeAll;
|
||||
|
||||
const UnionTransactionCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.transactions,
|
||||
this.onSeeAll,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: UnionFlowColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: UnionFlowColors.softShadow,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.textPrimary,
|
||||
),
|
||||
),
|
||||
if (onSeeAll != null)
|
||||
GestureDetector(
|
||||
onTap: onSeeAll,
|
||||
child: const Text(
|
||||
'Voir tout',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: UnionFlowColors.unionGreen,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Transactions
|
||||
...transactions,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/unionflow_colors.dart';
|
||||
|
||||
/// Carte premium affichant la vue unifiée "Compte Adhérent" du membre.
|
||||
class UnionUnifiedAccountCard extends StatelessWidget {
|
||||
final String numeroMembre;
|
||||
final String organisationNom;
|
||||
final String soldeTotal;
|
||||
final String capaciteEmprunt;
|
||||
final String epargneBloquee;
|
||||
final double engagementRate; // 0.0 to 1.0
|
||||
final VoidCallback? onDetailsTap;
|
||||
|
||||
const UnionUnifiedAccountCard({
|
||||
super.key,
|
||||
required this.numeroMembre,
|
||||
required this.organisationNom,
|
||||
required this.soldeTotal,
|
||||
required this.capaciteEmprunt,
|
||||
required this.epargneBloquee,
|
||||
required this.engagementRate,
|
||||
this.onDetailsTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
UnionFlowColors.unionGreen,
|
||||
UnionFlowColors.unionGreen.withOpacity(0.85),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: UnionFlowColors.unionGreen.withOpacity(0.35),
|
||||
offset: const Offset(0, 10),
|
||||
blurRadius: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Pattern décoratif subtil (fond)
|
||||
Positioned(
|
||||
right: -20,
|
||||
bottom: -20,
|
||||
child: Icon(
|
||||
Icons.account_balance_wallet,
|
||||
size: 150,
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// En-tête : Organisation + Numéro
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
organisationNom.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'COMPTE ADHÉRENT',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.95),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.white30),
|
||||
),
|
||||
child: Text(
|
||||
numeroMembre,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontFamily: 'Courier', // Look like a card number
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Solde Total Disponible
|
||||
const Text(
|
||||
'Solde Total Disponible',
|
||||
style: TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
soldeTotal,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Grille de détails
|
||||
Row(
|
||||
children: [
|
||||
_buildSubStat('Capacité Emprunt', capaciteEmprunt, Icons.rocket_launch, UnionFlowColors.gold),
|
||||
const SizedBox(width: 16),
|
||||
_buildSubStat('Épargne Bloquée', epargneBloquee, Icons.lock_clock, Colors.white60),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Barre d'engagement
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Taux d\'engagement (Cotisations)',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 10, fontWeight: FontWeight.w600),
|
||||
),
|
||||
Text(
|
||||
'${(engagementRate * 100).toInt()}%',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.w800),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: engagementRate,
|
||||
backgroundColor: Colors.white10,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(UnionFlowColors.gold),
|
||||
minHeight: 6,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSubStat(String label, String value, IconData icon, Color iconColor) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, size: 12, color: iconColor),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 10, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
337
lib/shared/design_system/dashboard_theme_manager.dart
Normal file
337
lib/shared/design_system/dashboard_theme_manager.dart
Normal file
@@ -0,0 +1,337 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// Gestionnaire de thèmes personnalisables pour le Dashboard
|
||||
class DashboardThemeManager {
|
||||
static const String _themeKey = 'dashboard_theme';
|
||||
static DashboardThemeData _currentTheme = DashboardThemeData.royalTeal();
|
||||
static SharedPreferences? _prefs;
|
||||
|
||||
/// Initialise le gestionnaire de thèmes
|
||||
static Future<void> initialize() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
await _loadSavedTheme();
|
||||
}
|
||||
|
||||
/// Charge le thème sauvegardé
|
||||
static Future<void> _loadSavedTheme() async {
|
||||
final themeName = _prefs?.getString(_themeKey) ?? 'royalTeal';
|
||||
_currentTheme = _getThemeByName(themeName);
|
||||
}
|
||||
|
||||
/// Obtient le thème actuel
|
||||
static DashboardThemeData get currentTheme => _currentTheme;
|
||||
|
||||
/// Change le thème et le sauvegarde
|
||||
static Future<void> setTheme(String themeName) async {
|
||||
_currentTheme = _getThemeByName(themeName);
|
||||
await _prefs?.setString(_themeKey, themeName);
|
||||
}
|
||||
|
||||
/// Obtient un thème par son nom
|
||||
static DashboardThemeData _getThemeByName(String name) {
|
||||
switch (name) {
|
||||
case 'royalTeal':
|
||||
return DashboardThemeData.royalTeal();
|
||||
case 'oceanBlue':
|
||||
return DashboardThemeData.oceanBlue();
|
||||
case 'forestGreen':
|
||||
return DashboardThemeData.forestGreen();
|
||||
case 'sunsetOrange':
|
||||
return DashboardThemeData.sunsetOrange();
|
||||
case 'purpleNight':
|
||||
return DashboardThemeData.purpleNight();
|
||||
case 'darkMode':
|
||||
return DashboardThemeData.darkMode();
|
||||
default:
|
||||
return DashboardThemeData.royalTeal();
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtient la liste des thèmes disponibles
|
||||
static List<ThemeOption> get availableThemes => [
|
||||
ThemeOption('royalTeal', 'Bleu Roi & Pétrole', DashboardThemeData.royalTeal()),
|
||||
ThemeOption('oceanBlue', 'Bleu Océan', DashboardThemeData.oceanBlue()),
|
||||
ThemeOption('forestGreen', 'Vert Forêt', DashboardThemeData.forestGreen()),
|
||||
ThemeOption('sunsetOrange', 'Orange Coucher', DashboardThemeData.sunsetOrange()),
|
||||
ThemeOption('purpleNight', 'Violet Nuit', DashboardThemeData.purpleNight()),
|
||||
ThemeOption('darkMode', 'Mode Sombre', DashboardThemeData.darkMode()),
|
||||
];
|
||||
}
|
||||
|
||||
/// Option de thème
|
||||
class ThemeOption {
|
||||
final String key;
|
||||
final String name;
|
||||
final DashboardThemeData theme;
|
||||
|
||||
const ThemeOption(this.key, this.name, this.theme);
|
||||
}
|
||||
|
||||
/// Données d'un thème de dashboard
|
||||
class DashboardThemeData {
|
||||
final String name;
|
||||
final Color primaryColor;
|
||||
final Color secondaryColor;
|
||||
final Color primaryLight;
|
||||
final Color primaryDark;
|
||||
final Color secondaryLight;
|
||||
final Color secondaryDark;
|
||||
final Color backgroundColor;
|
||||
final Color surfaceColor;
|
||||
final Color cardColor;
|
||||
final Color textPrimary;
|
||||
final Color textSecondary;
|
||||
final Color success;
|
||||
final Color warning;
|
||||
final Color error;
|
||||
final Color info;
|
||||
final bool isDark;
|
||||
|
||||
const DashboardThemeData({
|
||||
required this.name,
|
||||
required this.primaryColor,
|
||||
required this.secondaryColor,
|
||||
required this.primaryLight,
|
||||
required this.primaryDark,
|
||||
required this.secondaryLight,
|
||||
required this.secondaryDark,
|
||||
required this.backgroundColor,
|
||||
required this.surfaceColor,
|
||||
required this.cardColor,
|
||||
required this.textPrimary,
|
||||
required this.textSecondary,
|
||||
required this.success,
|
||||
required this.warning,
|
||||
required this.error,
|
||||
required this.info,
|
||||
this.isDark = false,
|
||||
});
|
||||
|
||||
/// Thème Bleu Roi & Pétrole (par défaut)
|
||||
factory DashboardThemeData.royalTeal() {
|
||||
return const DashboardThemeData(
|
||||
name: 'Bleu Roi & Pétrole',
|
||||
primaryColor: Color(0xFF4169E1),
|
||||
secondaryColor: Color(0xFF008B8B),
|
||||
primaryLight: Color(0xFF6A8EF7),
|
||||
primaryDark: Color(0xFF2E4BC6),
|
||||
secondaryLight: Color(0xFF20B2AA),
|
||||
secondaryDark: Color(0xFF006666),
|
||||
backgroundColor: Color(0xFFF9FAFB),
|
||||
surfaceColor: Color(0xFFFFFFFF),
|
||||
cardColor: Color(0xFFFFFFFF),
|
||||
textPrimary: Color(0xFF111827),
|
||||
textSecondary: Color(0xFF6B7280),
|
||||
success: Color(0xFF10B981),
|
||||
warning: Color(0xFFF59E0B),
|
||||
error: Color(0xFFEF4444),
|
||||
info: Color(0xFF3B82F6),
|
||||
);
|
||||
}
|
||||
|
||||
/// Thème Bleu Océan
|
||||
factory DashboardThemeData.oceanBlue() {
|
||||
return const DashboardThemeData(
|
||||
name: 'Bleu Océan',
|
||||
primaryColor: Color(0xFF0EA5E9),
|
||||
secondaryColor: Color(0xFF0284C7),
|
||||
primaryLight: Color(0xFF38BDF8),
|
||||
primaryDark: Color(0xFF0369A1),
|
||||
secondaryLight: Color(0xFF0EA5E9),
|
||||
secondaryDark: Color(0xFF075985),
|
||||
backgroundColor: Color(0xFFF0F9FF),
|
||||
surfaceColor: Color(0xFFFFFFFF),
|
||||
cardColor: Color(0xFFFFFFFF),
|
||||
textPrimary: Color(0xFF0C4A6E),
|
||||
textSecondary: Color(0xFF64748B),
|
||||
success: Color(0xFF059669),
|
||||
warning: Color(0xFFD97706),
|
||||
error: Color(0xFFDC2626),
|
||||
info: Color(0xFF2563EB),
|
||||
);
|
||||
}
|
||||
|
||||
/// Thème Vert Forêt
|
||||
factory DashboardThemeData.forestGreen() {
|
||||
return const DashboardThemeData(
|
||||
name: 'Vert Forêt',
|
||||
primaryColor: Color(0xFF059669),
|
||||
secondaryColor: Color(0xFF047857),
|
||||
primaryLight: Color(0xFF10B981),
|
||||
primaryDark: Color(0xFF065F46),
|
||||
secondaryLight: Color(0xFF059669),
|
||||
secondaryDark: Color(0xFF064E3B),
|
||||
backgroundColor: Color(0xFFF0FDF4),
|
||||
surfaceColor: Color(0xFFFFFFFF),
|
||||
cardColor: Color(0xFFFFFFFF),
|
||||
textPrimary: Color(0xFF064E3B),
|
||||
textSecondary: Color(0xFF6B7280),
|
||||
success: Color(0xFF10B981),
|
||||
warning: Color(0xFFF59E0B),
|
||||
error: Color(0xFFEF4444),
|
||||
info: Color(0xFF3B82F6),
|
||||
);
|
||||
}
|
||||
|
||||
/// Thème Orange Coucher de Soleil
|
||||
factory DashboardThemeData.sunsetOrange() {
|
||||
return const DashboardThemeData(
|
||||
name: 'Orange Coucher',
|
||||
primaryColor: Color(0xFFEA580C),
|
||||
secondaryColor: Color(0xFFDC2626),
|
||||
primaryLight: Color(0xFFF97316),
|
||||
primaryDark: Color(0xFFC2410C),
|
||||
secondaryLight: Color(0xFFEF4444),
|
||||
secondaryDark: Color(0xFFB91C1C),
|
||||
backgroundColor: Color(0xFFFFF7ED),
|
||||
surfaceColor: Color(0xFFFFFFFF),
|
||||
cardColor: Color(0xFFFFFFFF),
|
||||
textPrimary: Color(0xFF9A3412),
|
||||
textSecondary: Color(0xFF78716C),
|
||||
success: Color(0xFF059669),
|
||||
warning: Color(0xFFF59E0B),
|
||||
error: Color(0xFFDC2626),
|
||||
info: Color(0xFF2563EB),
|
||||
);
|
||||
}
|
||||
|
||||
/// Thème Violet Nuit
|
||||
factory DashboardThemeData.purpleNight() {
|
||||
return const DashboardThemeData(
|
||||
name: 'Violet Nuit',
|
||||
primaryColor: Color(0xFF7C3AED),
|
||||
secondaryColor: Color(0xFF9333EA),
|
||||
primaryLight: Color(0xFF8B5CF6),
|
||||
primaryDark: Color(0xFF5B21B6),
|
||||
secondaryLight: Color(0xFFA855F7),
|
||||
secondaryDark: Color(0xFF7E22CE),
|
||||
backgroundColor: Color(0xFFFAF5FF),
|
||||
surfaceColor: Color(0xFFFFFFFF),
|
||||
cardColor: Color(0xFFFFFFFF),
|
||||
textPrimary: Color(0xFF581C87),
|
||||
textSecondary: Color(0xFF6B7280),
|
||||
success: Color(0xFF059669),
|
||||
warning: Color(0xFFF59E0B),
|
||||
error: Color(0xFFEF4444),
|
||||
info: Color(0xFF3B82F6),
|
||||
);
|
||||
}
|
||||
|
||||
/// Thème Mode Sombre
|
||||
factory DashboardThemeData.darkMode() {
|
||||
return const DashboardThemeData(
|
||||
name: 'Mode Sombre',
|
||||
primaryColor: Color(0xFF60A5FA),
|
||||
secondaryColor: Color(0xFF34D399),
|
||||
primaryLight: Color(0xFF93C5FD),
|
||||
primaryDark: Color(0xFF3B82F6),
|
||||
secondaryLight: Color(0xFF6EE7B7),
|
||||
secondaryDark: Color(0xFF10B981),
|
||||
backgroundColor: Color(0xFF111827),
|
||||
surfaceColor: Color(0xFF1F2937),
|
||||
cardColor: Color(0xFF374151),
|
||||
textPrimary: Color(0xFFF9FAFB),
|
||||
textSecondary: Color(0xFFD1D5DB),
|
||||
success: Color(0xFF34D399),
|
||||
warning: Color(0xFFFBBF24),
|
||||
error: Color(0xFFF87171),
|
||||
info: Color(0xFF60A5FA),
|
||||
isDark: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// Gradient primaire
|
||||
LinearGradient get primaryGradient => LinearGradient(
|
||||
colors: [primaryColor, secondaryColor],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
|
||||
/// Gradient de carte
|
||||
LinearGradient get cardGradient => LinearGradient(
|
||||
colors: [
|
||||
cardColor,
|
||||
isDark ? surfaceColor : const Color(0xFFF8FAFC),
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
);
|
||||
|
||||
/// Gradient d'en-tête
|
||||
LinearGradient get headerGradient => LinearGradient(
|
||||
colors: [primaryColor, primaryDark],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
|
||||
/// Style de bouton primaire
|
||||
ButtonStyle get primaryButtonStyle => ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: isDark ? textPrimary : Colors.white,
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
);
|
||||
|
||||
/// Style de bouton secondaire
|
||||
ButtonStyle get secondaryButtonStyle => OutlinedButton.styleFrom(
|
||||
foregroundColor: primaryColor,
|
||||
side: BorderSide(color: primaryColor),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
);
|
||||
|
||||
/// Thème Flutter complet
|
||||
ThemeData get flutterTheme => ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: isDark ? Brightness.dark : Brightness.light,
|
||||
primaryColor: primaryColor,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
brightness: isDark ? Brightness.dark : Brightness.light,
|
||||
secondary: secondaryColor,
|
||||
surface: surfaceColor,
|
||||
background: backgroundColor,
|
||||
),
|
||||
scaffoldBackgroundColor: backgroundColor,
|
||||
cardColor: cardColor,
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: isDark ? textPrimary : Colors.white,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(style: primaryButtonStyle),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(style: secondaryButtonStyle),
|
||||
cardTheme: CardTheme(
|
||||
color: cardColor,
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
textTheme: TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textPrimary,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textPrimary,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
fontSize: 16,
|
||||
color: textPrimary,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
fontSize: 14,
|
||||
color: textSecondary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
141
lib/shared/design_system/theme/app_theme.dart
Normal file
141
lib/shared/design_system/theme/app_theme.dart
Normal file
@@ -0,0 +1,141 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/app_colors.dart';
|
||||
import '../tokens/app_typography.dart';
|
||||
|
||||
/// UnionFlow Mobile App - Thème Global
|
||||
/// Utilise la charte stricte (Vert/Blanc/Noir OLED) et force la petite typographie.
|
||||
class AppTheme {
|
||||
|
||||
// --- THÈME CLAIR (Mode Jour) ---
|
||||
static final ThemeData lightTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
primaryColor: AppColors.primaryGreen,
|
||||
scaffoldBackgroundColor: AppColors.lightSurface,
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: AppColors.primaryGreen,
|
||||
secondary: AppColors.brandGreenLight,
|
||||
surface: AppColors.lightBackground,
|
||||
error: AppColors.error,
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onSurface: AppColors.textPrimaryLight,
|
||||
onError: Colors.white,
|
||||
),
|
||||
|
||||
// Forcer la typographie standardisée
|
||||
textTheme: const TextTheme(
|
||||
titleMedium: AppTypography.headerSmall,
|
||||
bodyMedium: AppTypography.bodyTextSmall,
|
||||
bodySmall: AppTypography.subtitleSmall,
|
||||
labelLarge: AppTypography.actionText,
|
||||
labelSmall: AppTypography.badgeText,
|
||||
),
|
||||
|
||||
// Personnalisation des AppBar (Garder la minimaliste)
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: AppColors.lightBackground,
|
||||
foregroundColor: AppColors.textPrimaryLight,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
iconTheme: IconThemeData(color: AppColors.textPrimaryLight, size: 20),
|
||||
titleTextStyle: AppTypography.headerSmall,
|
||||
),
|
||||
|
||||
// Boutons par défaut
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
textStyle: AppTypography.actionText,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
minimumSize: const Size(64, 32),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
),
|
||||
|
||||
// BottomNavigationBar ultra-compacte
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
backgroundColor: AppColors.lightBackground,
|
||||
selectedItemColor: AppColors.primaryGreen,
|
||||
unselectedItemColor: AppColors.textSecondaryLight,
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
elevation: 8,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
),
|
||||
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: AppColors.lightBorder,
|
||||
thickness: 1,
|
||||
space: 1,
|
||||
),
|
||||
);
|
||||
|
||||
// --- THÈME SOMBRE (Mode Nuit OLED) ---
|
||||
static final ThemeData darkTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: AppColors.primaryGreen,
|
||||
scaffoldBackgroundColor: AppColors.darkBackground, // Noir OLED
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: AppColors.primaryGreen,
|
||||
secondary: AppColors.brandGreenLight,
|
||||
surface: AppColors.darkSurface, // Gris très sombre
|
||||
error: AppColors.error,
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onSurface: AppColors.textPrimaryDark,
|
||||
onError: Colors.white,
|
||||
),
|
||||
|
||||
textTheme: const TextTheme(
|
||||
titleMedium: AppTypography.headerSmall,
|
||||
bodyMedium: AppTypography.bodyTextSmall,
|
||||
bodySmall: AppTypography.subtitleSmall,
|
||||
labelLarge: AppTypography.actionText,
|
||||
labelSmall: AppTypography.badgeText,
|
||||
).apply(
|
||||
bodyColor: AppColors.textPrimaryDark,
|
||||
displayColor: AppColors.textPrimaryDark,
|
||||
),
|
||||
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: AppColors.darkBackground,
|
||||
foregroundColor: AppColors.textPrimaryDark,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
iconTheme: IconThemeData(color: AppColors.textPrimaryDark, size: 20),
|
||||
titleTextStyle: AppTypography.headerSmall, // Remplace titleTextStyle
|
||||
),
|
||||
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
textStyle: AppTypography.actionText,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
minimumSize: const Size(64, 32),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
),
|
||||
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
backgroundColor: AppColors.darkBackground,
|
||||
selectedItemColor: AppColors.primaryGreen,
|
||||
unselectedItemColor: AppColors.textSecondaryDark,
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
elevation: 8,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
),
|
||||
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: AppColors.darkBorder,
|
||||
thickness: 1,
|
||||
space: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
473
lib/shared/design_system/theme/app_theme_sophisticated.dart
Normal file
473
lib/shared/design_system/theme/app_theme_sophisticated.dart
Normal file
@@ -0,0 +1,473 @@
|
||||
/// Thème Sophistiqué UnionFlow
|
||||
///
|
||||
/// Implémentation complète du design system avec les dernières tendances UI/UX 2024-2025
|
||||
/// Architecture modulaire et tokens de design cohérents
|
||||
library app_theme_sophisticated;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../tokens/color_tokens.dart';
|
||||
import '../tokens/typography_tokens.dart';
|
||||
import '../tokens/spacing_tokens.dart';
|
||||
|
||||
/// Thème principal de l'application UnionFlow
|
||||
class AppThemeSophisticated {
|
||||
AppThemeSophisticated._();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// THÈME PRINCIPAL - Configuration complète
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Thème clair principal
|
||||
static ThemeData get lightTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
|
||||
// Couleurs principales
|
||||
colorScheme: _lightColorScheme,
|
||||
|
||||
// Typographie
|
||||
textTheme: _textTheme,
|
||||
|
||||
// Configuration de l'AppBar
|
||||
appBarTheme: _appBarTheme,
|
||||
|
||||
// Configuration des cartes
|
||||
cardTheme: _cardTheme,
|
||||
|
||||
// Configuration des boutons
|
||||
elevatedButtonTheme: _elevatedButtonTheme,
|
||||
filledButtonTheme: _filledButtonTheme,
|
||||
outlinedButtonTheme: _outlinedButtonTheme,
|
||||
textButtonTheme: _textButtonTheme,
|
||||
|
||||
// Configuration des champs de saisie
|
||||
inputDecorationTheme: _inputDecorationTheme,
|
||||
|
||||
// Configuration de la navigation
|
||||
navigationBarTheme: _navigationBarTheme,
|
||||
navigationDrawerTheme: _navigationDrawerTheme,
|
||||
|
||||
// Configuration des dialogues
|
||||
dialogTheme: _dialogTheme,
|
||||
|
||||
// Configuration des snackbars
|
||||
snackBarTheme: _snackBarTheme,
|
||||
|
||||
// Configuration des puces
|
||||
chipTheme: _chipTheme,
|
||||
|
||||
// Configuration des listes
|
||||
listTileTheme: _listTileTheme,
|
||||
|
||||
// Configuration des onglets
|
||||
tabBarTheme: _tabBarTheme,
|
||||
|
||||
// Configuration des dividers
|
||||
dividerTheme: _dividerTheme,
|
||||
|
||||
// Configuration des icônes
|
||||
iconTheme: _iconTheme,
|
||||
|
||||
// Configuration des surfaces
|
||||
scaffoldBackgroundColor: ColorTokens.surface,
|
||||
canvasColor: ColorTokens.surface,
|
||||
|
||||
// Configuration des animations
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
|
||||
// Configuration des extensions
|
||||
extensions: const [
|
||||
_customColors,
|
||||
_customSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Thème sombre (suit le système ou sélection manuelle)
|
||||
static ThemeData get darkTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: ColorScheme.dark(
|
||||
primary: ColorTokens.primary,
|
||||
onPrimary: Colors.white,
|
||||
surface: const Color(0xFF121212),
|
||||
onSurface: Colors.white,
|
||||
error: ColorTokens.error,
|
||||
),
|
||||
scaffoldBackgroundColor: const Color(0xFF121212),
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// SCHÉMA DE COULEURS
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
static const ColorScheme _lightColorScheme = ColorScheme.light(
|
||||
// Couleurs primaires
|
||||
primary: ColorTokens.primary,
|
||||
onPrimary: ColorTokens.onPrimary,
|
||||
primaryContainer: ColorTokens.primaryContainer,
|
||||
onPrimaryContainer: ColorTokens.onPrimaryContainer,
|
||||
|
||||
// Couleurs secondaires
|
||||
secondary: ColorTokens.secondary,
|
||||
onSecondary: ColorTokens.onSecondary,
|
||||
secondaryContainer: ColorTokens.secondaryContainer,
|
||||
onSecondaryContainer: ColorTokens.onSecondaryContainer,
|
||||
|
||||
// Couleurs tertiaires
|
||||
tertiary: ColorTokens.tertiary,
|
||||
onTertiary: ColorTokens.onTertiary,
|
||||
tertiaryContainer: ColorTokens.tertiaryContainer,
|
||||
onTertiaryContainer: ColorTokens.onTertiaryContainer,
|
||||
|
||||
// Couleurs d'erreur
|
||||
error: ColorTokens.error,
|
||||
onError: ColorTokens.onError,
|
||||
errorContainer: ColorTokens.errorContainer,
|
||||
onErrorContainer: ColorTokens.onErrorContainer,
|
||||
|
||||
// Couleurs de surface
|
||||
surface: ColorTokens.surface,
|
||||
onSurface: ColorTokens.onSurface,
|
||||
surfaceContainerHighest: ColorTokens.surfaceVariant,
|
||||
onSurfaceVariant: ColorTokens.onSurfaceVariant,
|
||||
|
||||
// Couleurs de contour
|
||||
outline: ColorTokens.outline,
|
||||
outlineVariant: ColorTokens.outlineVariant,
|
||||
|
||||
// Couleurs d'ombre
|
||||
shadow: ColorTokens.shadow,
|
||||
scrim: ColorTokens.shadow,
|
||||
|
||||
// Couleurs d'inversion
|
||||
inverseSurface: ColorTokens.onSurface,
|
||||
onInverseSurface: ColorTokens.surface,
|
||||
inversePrimary: ColorTokens.primaryLight,
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// THÈME TYPOGRAPHIQUE
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
static const TextTheme _textTheme = TextTheme(
|
||||
// Display styles
|
||||
displayLarge: TypographyTokens.displayLarge,
|
||||
displayMedium: TypographyTokens.displayMedium,
|
||||
displaySmall: TypographyTokens.displaySmall,
|
||||
|
||||
// Headline styles
|
||||
headlineLarge: TypographyTokens.headlineLarge,
|
||||
headlineMedium: TypographyTokens.headlineMedium,
|
||||
headlineSmall: TypographyTokens.headlineSmall,
|
||||
|
||||
// Title styles
|
||||
titleLarge: TypographyTokens.titleLarge,
|
||||
titleMedium: TypographyTokens.titleMedium,
|
||||
titleSmall: TypographyTokens.titleSmall,
|
||||
|
||||
// Label styles
|
||||
labelLarge: TypographyTokens.labelLarge,
|
||||
labelMedium: TypographyTokens.labelMedium,
|
||||
labelSmall: TypographyTokens.labelSmall,
|
||||
|
||||
// Body styles
|
||||
bodyLarge: TypographyTokens.bodyLarge,
|
||||
bodyMedium: TypographyTokens.bodyMedium,
|
||||
bodySmall: TypographyTokens.bodySmall,
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// THÈMES DE COMPOSANTS
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Configuration AppBar moderne (sans AppBar traditionnelle)
|
||||
static const AppBarTheme _appBarTheme = AppBarTheme(
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: ColorTokens.onSurface,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
statusBarBrightness: Brightness.light,
|
||||
),
|
||||
);
|
||||
|
||||
/// Configuration des cartes sophistiquées
|
||||
static final CardTheme _cardTheme = CardTheme(
|
||||
elevation: SpacingTokens.elevationSm,
|
||||
shadowColor: ColorTokens.shadow,
|
||||
surfaceTintColor: ColorTokens.surfaceContainer,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
|
||||
),
|
||||
margin: const EdgeInsets.all(SpacingTokens.cardMargin),
|
||||
);
|
||||
|
||||
/// Configuration des boutons élevés
|
||||
static final ElevatedButtonThemeData _elevatedButtonTheme = ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: SpacingTokens.elevationSm,
|
||||
shadowColor: ColorTokens.shadow,
|
||||
backgroundColor: ColorTokens.primary,
|
||||
foregroundColor: ColorTokens.onPrimary,
|
||||
textStyle: TypographyTokens.buttonMedium,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.buttonPaddingHorizontal,
|
||||
vertical: SpacingTokens.buttonPaddingVertical,
|
||||
),
|
||||
minimumSize: const Size(
|
||||
SpacingTokens.minButtonWidth,
|
||||
SpacingTokens.buttonHeightMedium,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/// Configuration des boutons remplis
|
||||
static final FilledButtonThemeData _filledButtonTheme = FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: ColorTokens.primary,
|
||||
foregroundColor: ColorTokens.onPrimary,
|
||||
textStyle: TypographyTokens.buttonMedium,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.buttonPaddingHorizontal,
|
||||
vertical: SpacingTokens.buttonPaddingVertical,
|
||||
),
|
||||
minimumSize: const Size(
|
||||
SpacingTokens.minButtonWidth,
|
||||
SpacingTokens.buttonHeightMedium,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/// Configuration des boutons avec contour
|
||||
static final OutlinedButtonThemeData _outlinedButtonTheme = OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: ColorTokens.primary,
|
||||
textStyle: TypographyTokens.buttonMedium,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.buttonPaddingHorizontal,
|
||||
vertical: SpacingTokens.buttonPaddingVertical,
|
||||
),
|
||||
minimumSize: const Size(
|
||||
SpacingTokens.minButtonWidth,
|
||||
SpacingTokens.buttonHeightMedium,
|
||||
),
|
||||
side: const BorderSide(
|
||||
color: ColorTokens.outline,
|
||||
width: 1.0,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/// Configuration des boutons texte
|
||||
static final TextButtonThemeData _textButtonTheme = TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: ColorTokens.primary,
|
||||
textStyle: TypographyTokens.buttonMedium,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.buttonPaddingHorizontal,
|
||||
vertical: SpacingTokens.buttonPaddingVertical,
|
||||
),
|
||||
minimumSize: const Size(
|
||||
SpacingTokens.minButtonWidth,
|
||||
SpacingTokens.buttonHeightMedium,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/// Configuration des champs de saisie
|
||||
static final InputDecorationTheme _inputDecorationTheme = InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: ColorTokens.surfaceContainer,
|
||||
labelStyle: TypographyTokens.inputLabel,
|
||||
hintStyle: TypographyTokens.inputHint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
borderSide: const BorderSide(color: ColorTokens.outline),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
borderSide: const BorderSide(color: ColorTokens.outline),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
borderSide: const BorderSide(color: ColorTokens.primary, width: 2.0),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
borderSide: const BorderSide(color: ColorTokens.error),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(SpacingTokens.formPadding),
|
||||
);
|
||||
|
||||
/// Configuration de la barre de navigation
|
||||
static final NavigationBarThemeData _navigationBarTheme = NavigationBarThemeData(
|
||||
backgroundColor: ColorTokens.navigationBackground,
|
||||
indicatorColor: ColorTokens.navigationIndicator,
|
||||
labelTextStyle: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return TypographyTokens.navigationLabelSelected;
|
||||
}
|
||||
return TypographyTokens.navigationLabel;
|
||||
}),
|
||||
iconTheme: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return const IconThemeData(color: ColorTokens.navigationSelected);
|
||||
}
|
||||
return const IconThemeData(color: ColorTokens.navigationUnselected);
|
||||
}),
|
||||
);
|
||||
|
||||
/// Configuration du drawer de navigation
|
||||
static final NavigationDrawerThemeData _navigationDrawerTheme = NavigationDrawerThemeData(
|
||||
backgroundColor: ColorTokens.surfaceContainer,
|
||||
elevation: SpacingTokens.elevationMd,
|
||||
shadowColor: ColorTokens.shadow,
|
||||
surfaceTintColor: ColorTokens.surfaceContainer,
|
||||
indicatorColor: ColorTokens.primaryContainer,
|
||||
labelTextStyle: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return TypographyTokens.navigationLabelSelected;
|
||||
}
|
||||
return TypographyTokens.navigationLabel;
|
||||
}),
|
||||
);
|
||||
|
||||
/// Configuration des dialogues
|
||||
static final DialogTheme _dialogTheme = DialogTheme(
|
||||
backgroundColor: ColorTokens.surfaceContainer,
|
||||
elevation: SpacingTokens.elevationLg,
|
||||
shadowColor: ColorTokens.shadow,
|
||||
surfaceTintColor: ColorTokens.surfaceContainer,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusXl),
|
||||
),
|
||||
titleTextStyle: TypographyTokens.headlineSmall,
|
||||
contentTextStyle: TypographyTokens.bodyMedium,
|
||||
);
|
||||
|
||||
/// Configuration des snackbars (fixed pour éviter "Floating SnackBar off screen" avec bottomNavigationBar)
|
||||
static final SnackBarThemeData _snackBarTheme = SnackBarThemeData(
|
||||
backgroundColor: ColorTokens.onSurface,
|
||||
contentTextStyle: TypographyTokens.bodyMedium.copyWith(
|
||||
color: ColorTokens.surface,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
behavior: SnackBarBehavior.fixed,
|
||||
);
|
||||
|
||||
/// Configuration des puces
|
||||
static final ChipThemeData _chipTheme = ChipThemeData(
|
||||
backgroundColor: ColorTokens.surfaceVariant,
|
||||
selectedColor: ColorTokens.primaryContainer,
|
||||
labelStyle: TypographyTokens.labelMedium,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.md,
|
||||
vertical: SpacingTokens.sm,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
),
|
||||
);
|
||||
|
||||
/// Configuration des éléments de liste
|
||||
static const ListTileThemeData _listTileTheme = ListTileThemeData(
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: SpacingTokens.xl,
|
||||
vertical: SpacingTokens.md,
|
||||
),
|
||||
titleTextStyle: TypographyTokens.titleMedium,
|
||||
subtitleTextStyle: TypographyTokens.bodyMedium,
|
||||
leadingAndTrailingTextStyle: TypographyTokens.labelMedium,
|
||||
minVerticalPadding: SpacingTokens.md,
|
||||
);
|
||||
|
||||
/// Configuration des onglets
|
||||
static final TabBarTheme _tabBarTheme = TabBarTheme(
|
||||
labelColor: ColorTokens.primary,
|
||||
unselectedLabelColor: ColorTokens.onSurfaceVariant,
|
||||
labelStyle: TypographyTokens.titleSmall,
|
||||
unselectedLabelStyle: TypographyTokens.titleSmall,
|
||||
indicator: UnderlineTabIndicator(
|
||||
borderSide: const BorderSide(
|
||||
color: ColorTokens.primary,
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusXs),
|
||||
),
|
||||
);
|
||||
|
||||
/// Configuration des dividers
|
||||
static const DividerThemeData _dividerTheme = DividerThemeData(
|
||||
color: ColorTokens.outline,
|
||||
thickness: 1.0,
|
||||
space: SpacingTokens.md,
|
||||
);
|
||||
|
||||
/// Configuration des icônes
|
||||
static const IconThemeData _iconTheme = IconThemeData(
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
size: 24.0,
|
||||
);
|
||||
|
||||
/// Configuration des transitions de page
|
||||
static const PageTransitionsTheme _pageTransitionsTheme = PageTransitionsTheme(
|
||||
builders: {
|
||||
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||
},
|
||||
);
|
||||
|
||||
/// Extensions personnalisées - Couleurs
|
||||
static const CustomColors _customColors = CustomColors();
|
||||
|
||||
/// Extensions personnalisées - Espacements
|
||||
static const CustomSpacing _customSpacing = CustomSpacing();
|
||||
}
|
||||
|
||||
/// Extension de couleurs personnalisées
|
||||
class CustomColors extends ThemeExtension<CustomColors> {
|
||||
const CustomColors();
|
||||
|
||||
@override
|
||||
CustomColors copyWith() => const CustomColors();
|
||||
|
||||
@override
|
||||
CustomColors lerp(ThemeExtension<CustomColors>? other, double t) {
|
||||
return const CustomColors();
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension d'espacements personnalisés
|
||||
class CustomSpacing extends ThemeExtension<CustomSpacing> {
|
||||
const CustomSpacing();
|
||||
|
||||
@override
|
||||
CustomSpacing copyWith() => const CustomSpacing();
|
||||
|
||||
@override
|
||||
CustomSpacing lerp(ThemeExtension<CustomSpacing>? other, double t) {
|
||||
return const CustomSpacing();
|
||||
}
|
||||
}
|
||||
37
lib/shared/design_system/tokens/app_colors.dart
Normal file
37
lib/shared/design_system/tokens/app_colors.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// UnionFlow Mobile App - Couleurs Globales (Strict DRY)
|
||||
/// Palette principale: Vert, Blanc (Jour), Noir (Nuit).
|
||||
class AppColors {
|
||||
// --- Branding ---
|
||||
static const Color primaryGreen = Color(0xFF17BF63); // Vert vibrant style social (Fb/Tw) - Corrigé.
|
||||
static const Color brandGreen = Color(0xFF2E7D32); // Vert professionnel et lisible
|
||||
static const Color brandGreenLight = Color(0xFF4CAF50); // Vert d'accentuation
|
||||
|
||||
// --- Mode Jour (Light) ---
|
||||
static const Color lightBackground = Color(0xFFFFFFFF); // Blanc pur
|
||||
static const Color lightSurface = Color(0xFFF5F8FA); // Gris extrêmement léger pour séparer les cards
|
||||
static const Color lightBorder = Color(0xFFE1E8ED); // Bordures style Twitter
|
||||
|
||||
// --- Mode Nuit (Dark OLED) ---
|
||||
static const Color darkBackground = Color(0xFF000000); // Noir pur pour OLED
|
||||
static const Color darkSurface = Color(0xFF15202B); // Gris sombre typique Twitter/Fb Dark
|
||||
static const Color darkBorder = Color(0xFF38444D); // Bordure discrète sombre
|
||||
|
||||
// --- Texte ---
|
||||
static const Color textPrimaryLight = Color(0xFF14171A); // Presque noir
|
||||
static const Color textSecondaryLight = Color(0xFF657786); // Gris texte
|
||||
static const Color textPrimaryDark = Color(0xFFE1E8ED); // Presque blanc
|
||||
static const Color textSecondaryDark = Color(0xFF8899A6); // Gris clair nuit
|
||||
|
||||
// --- Sémantique (Succès, Erreur, Info) ---
|
||||
static const Color error = Color(0xFFE0245E); // Rouge vif
|
||||
static const Color success = Color(0xFF17BF63); // Vert validation
|
||||
static const Color warning = Color(0xFFFFAD1F); // Orange
|
||||
static const Color info = Color(0xFF1DA1F2); // Bleu info
|
||||
|
||||
// --- Utilitaires ---
|
||||
static const Color transparent = Colors.transparent;
|
||||
static const Color surface = lightSurface;
|
||||
static const Color background = lightBackground;
|
||||
}
|
||||
48
lib/shared/design_system/tokens/app_typography.dart
Normal file
48
lib/shared/design_system/tokens/app_typography.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'app_colors.dart';
|
||||
|
||||
/// UnionFlow Mobile App - Typographie Globale (Ultra Minimaliste)
|
||||
/// RÈGLE : AUCUN gros titre. Tailles limitées entre 10px et 14px pour maximiser l'information.
|
||||
class AppTypography {
|
||||
static const String _fontFamily = 'Roboto'; // Peut être changé pour 'Inter' si ajouté au pubspec.yaml
|
||||
|
||||
// --- Titres (Max 14px) ---
|
||||
static const TextStyle headerSmall = TextStyle(
|
||||
fontFamily: _fontFamily,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w700, // Bold
|
||||
letterSpacing: -0.2,
|
||||
);
|
||||
|
||||
// --- Corps de texte (Max 12px) ---
|
||||
static const TextStyle bodyTextSmall = TextStyle(
|
||||
fontFamily: _fontFamily,
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.w400, // Regular
|
||||
height: 1.4,
|
||||
);
|
||||
|
||||
// --- Boutons et Actions (Max 13px) ---
|
||||
static const TextStyle actionText = TextStyle(
|
||||
fontFamily: _fontFamily,
|
||||
fontSize: 13.0,
|
||||
fontWeight: FontWeight.w600, // SemiBold
|
||||
letterSpacing: 0.1,
|
||||
);
|
||||
|
||||
// --- Sous-titres, dates, labels (Max 11px) ---
|
||||
static const TextStyle subtitleSmall = TextStyle(
|
||||
fontFamily: _fontFamily,
|
||||
fontSize: 11.0,
|
||||
fontWeight: FontWeight.w300, // Light
|
||||
letterSpacing: 0.2,
|
||||
);
|
||||
|
||||
// --- Badges, Piles, Métriques très denses (Max 10px) ---
|
||||
static const TextStyle badgeText = TextStyle(
|
||||
fontFamily: _fontFamily,
|
||||
fontSize: 10.0,
|
||||
fontWeight: FontWeight.w500, // Medium
|
||||
);
|
||||
}
|
||||
197
lib/shared/design_system/tokens/color_tokens.dart
Normal file
197
lib/shared/design_system/tokens/color_tokens.dart
Normal file
@@ -0,0 +1,197 @@
|
||||
/// Design Tokens - Couleurs UnionFlow
|
||||
///
|
||||
/// Palette de couleurs Bleu Roi + Bleu Pétrole
|
||||
/// Inspirée des tendances UI/UX 2024-2025
|
||||
/// Basée sur les principes de Material Design 3
|
||||
///
|
||||
/// MODE JOUR: Bleu Roi (#4169E1) - Royal Blue
|
||||
/// MODE NUIT: Bleu Pétrole (#2C5F6F) - Petroleum Blue
|
||||
library color_tokens;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Tokens de couleurs UnionFlow - Design System Unifié
|
||||
class ColorTokens {
|
||||
ColorTokens._();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COULEURS PRIMAIRES - MODE JOUR (Bleu Roi)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Couleur primaire principale - Bleu Roi (Royal Blue)
|
||||
static const Color primary = Color(0xFF4169E1); // Bleu roi
|
||||
static const Color primaryLight = Color(0xFF6B8EF5); // Bleu roi clair
|
||||
static const Color primaryDark = Color(0xFF2952C8); // Bleu roi sombre
|
||||
static const Color primaryContainer = Color(0xFFE3ECFF); // Container bleu roi
|
||||
static const Color onPrimary = Color(0xFFFFFFFF); // Texte sur primaire (blanc)
|
||||
static const Color onPrimaryContainer = Color(0xFF001A41); // Texte sur container
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COULEURS PRIMAIRES - MODE NUIT (Bleu Pétrole)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Couleur primaire mode nuit - Bleu Pétrole
|
||||
static const Color primaryDarkMode = Color(0xFF2C5F6F); // Bleu pétrole
|
||||
static const Color primaryLightDarkMode = Color(0xFF3D7A8C); // Bleu pétrole clair
|
||||
static const Color primaryDarkDarkMode = Color(0xFF1B4D5C); // Bleu pétrole sombre
|
||||
static const Color primaryContainerDarkMode = Color(0xFF1E3A44); // Container mode nuit
|
||||
static const Color onPrimaryDarkMode = Color(0xFFE5E7EB); // Texte sur primaire (gris clair)
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COULEURS SECONDAIRES - Indigo Moderne
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
static const Color secondary = Color(0xFF6366F1); // Indigo moderne
|
||||
static const Color secondaryLight = Color(0xFF8B8FF6); // Indigo clair
|
||||
static const Color secondaryDark = Color(0xFF4F46E5); // Indigo sombre
|
||||
static const Color secondaryContainer = Color(0xFFE0E7FF); // Container indigo
|
||||
static const Color onSecondary = Color(0xFFFFFFFF);
|
||||
static const Color onSecondaryContainer = Color(0xFF1E1B3A);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COULEURS TERTIAIRES - Vert Émeraude
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
static const Color tertiary = Color(0xFF10B981); // Vert émeraude
|
||||
static const Color tertiaryLight = Color(0xFF34D399); // Vert clair
|
||||
static const Color tertiaryDark = Color(0xFF059669); // Vert sombre
|
||||
static const Color tertiaryContainer = Color(0xFFD1FAE5); // Container vert
|
||||
static const Color onTertiary = Color(0xFFFFFFFF);
|
||||
static const Color onTertiaryContainer = Color(0xFF002114);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COULEURS NEUTRES - MODE JOUR
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
static const Color surface = Color(0xFFFFFFFF); // Surface principale (blanc)
|
||||
static const Color surfaceVariant = Color(0xFFF8F9FA); // Surface variante (gris très clair)
|
||||
static const Color surfaceContainer = Color(0xFFFFFFFF); // Container surface
|
||||
static const Color surfaceContainerHigh = Color(0xFFF8F9FA); // Container élevé
|
||||
static const Color surfaceContainerHighest = Color(0xFFE5E7EB); // Container max
|
||||
static const Color background = Color(0xFFF8F9FA); // Background général
|
||||
|
||||
static const Color onSurface = Color(0xFF1F2937); // Texte principal (gris très foncé)
|
||||
static const Color onSurfaceVariant = Color(0xFF6B7280); // Texte secondaire (gris moyen)
|
||||
static const Color textSecondary = Color(0xFF6B7280); // Texte secondaire (alias)
|
||||
static const Color outline = Color(0xFFD1D5DB); // Bordures
|
||||
static const Color outlineVariant = Color(0xFFE5E7EB); // Bordures claires
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COULEURS NEUTRES - MODE NUIT
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
static const Color surfaceDarkMode = Color(0xFF1E1E1E); // Surface principale (gris très sombre)
|
||||
static const Color surfaceVariantDarkMode = Color(0xFF2C2C2C); // Surface variante
|
||||
static const Color backgroundDarkMode = Color(0xFF121212); // Background général (noir profond)
|
||||
|
||||
static const Color onSurfaceDarkMode = Color(0xFFE5E7EB); // Texte principal (gris très clair)
|
||||
static const Color onSurfaceVariantDarkMode = Color(0xFF9CA3AF); // Texte secondaire (gris moyen)
|
||||
static const Color outlineDarkMode = Color(0xFF4B5563); // Bordures mode nuit
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COULEURS SÉMANTIQUES - États et feedback
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Couleurs de succès
|
||||
static const Color success = Color(0xFF10B981); // Vert succès
|
||||
static const Color successLight = Color(0xFF34D399); // Vert clair
|
||||
static const Color successDark = Color(0xFF059669); // Vert sombre
|
||||
static const Color successContainer = Color(0xFFECFDF5); // Container succès
|
||||
static const Color onSuccess = Color(0xFFFFFFFF);
|
||||
static const Color onSuccessContainer = Color(0xFF002114);
|
||||
|
||||
/// Couleurs d'erreur
|
||||
static const Color error = Color(0xFFDC2626); // Rouge erreur
|
||||
static const Color errorLight = Color(0xFFEF4444); // Rouge clair
|
||||
static const Color errorDark = Color(0xFFB91C1C); // Rouge sombre
|
||||
static const Color errorContainer = Color(0xFFFEF2F2); // Container erreur
|
||||
static const Color onError = Color(0xFFFFFFFF);
|
||||
static const Color onErrorContainer = Color(0xFF410002);
|
||||
|
||||
/// Couleurs d'avertissement
|
||||
static const Color warning = Color(0xFFF59E0B); // Orange avertissement
|
||||
static const Color warningLight = Color(0xFFFBBF24); // Orange clair
|
||||
static const Color warningDark = Color(0xFFD97706); // Orange sombre
|
||||
static const Color warningContainer = Color(0xFFFEF3C7); // Container avertissement
|
||||
static const Color onWarning = Color(0xFFFFFFFF);
|
||||
static const Color onWarningContainer = Color(0xFF2D1B00);
|
||||
|
||||
/// Couleurs d'information
|
||||
static const Color info = Color(0xFF0EA5E9); // Bleu info
|
||||
static const Color infoLight = Color(0xFF38BDF8); // Bleu clair
|
||||
static const Color infoDark = Color(0xFF0284C7); // Bleu sombre
|
||||
static const Color infoContainer = Color(0xFFE0F2FE); // Container info
|
||||
static const Color onInfo = Color(0xFFFFFFFF);
|
||||
static const Color onInfoContainer = Color(0xFF001D36);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COULEURS SPÉCIALISÉES - Interface avancée
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Couleurs de navigation - Mode Jour
|
||||
static const Color navigationBackground = Color(0xFFFFFFFF);
|
||||
static const Color navigationSelected = Color(0xFF4169E1); // Bleu roi
|
||||
static const Color navigationUnselected = Color(0xFF6B7280);
|
||||
static const Color navigationIndicator = Color(0xFF4169E1); // Bleu roi
|
||||
|
||||
/// Couleurs de navigation - Mode Nuit
|
||||
static const Color navigationBackgroundDarkMode = Color(0xFF1E1E1E);
|
||||
static const Color navigationSelectedDarkMode = Color(0xFF2C5F6F); // Bleu pétrole
|
||||
static const Color navigationUnselectedDarkMode = Color(0xFF9CA3AF);
|
||||
static const Color navigationIndicatorDarkMode = Color(0xFF2C5F6F); // Bleu pétrole
|
||||
|
||||
/// Couleurs d'élévation et ombres
|
||||
static const Color shadow = Color(0x1A000000); // Ombre légère
|
||||
static const Color shadowMedium = Color(0x33000000); // Ombre moyenne
|
||||
static const Color shadowHigh = Color(0x4D000000); // Ombre forte
|
||||
|
||||
/// Couleurs de glassmorphism (tendance 2024-2025)
|
||||
static const Color glassBackground = Color(0x80FFFFFF); // Fond verre
|
||||
static const Color glassBorder = Color(0x33FFFFFF); // Bordure verre
|
||||
static const Color glassOverlay = Color(0x0DFFFFFF); // Overlay verre
|
||||
|
||||
/// Couleurs de gradient - Mode Jour (Bleu Roi)
|
||||
static const List<Color> primaryGradient = [
|
||||
Color(0xFF4169E1), // Bleu roi
|
||||
Color(0xFF6B8EF5), // Bleu roi clair
|
||||
];
|
||||
|
||||
/// Couleurs de gradient - Mode Nuit (Bleu Pétrole)
|
||||
static const List<Color> primaryGradientDarkMode = [
|
||||
Color(0xFF2C5F6F), // Bleu pétrole
|
||||
Color(0xFF3D7A8C), // Bleu pétrole clair
|
||||
];
|
||||
|
||||
static const List<Color> secondaryGradient = [
|
||||
Color(0xFF6366F1), // Indigo
|
||||
Color(0xFF8B8FF6), // Indigo clair
|
||||
];
|
||||
|
||||
static const List<Color> successGradient = [
|
||||
Color(0xFF10B981), // Vert émeraude
|
||||
Color(0xFF34D399), // Vert clair
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// MÉTHODES UTILITAIRES
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Obtient une couleur avec opacité
|
||||
static Color withOpacity(Color color, double opacity) {
|
||||
return color.withOpacity(opacity);
|
||||
}
|
||||
|
||||
/// Obtient une couleur plus claire
|
||||
static Color lighten(Color color, [double amount = 0.1]) {
|
||||
final hsl = HSLColor.fromColor(color);
|
||||
final lightness = (hsl.lightness + amount).clamp(0.0, 1.0);
|
||||
return hsl.withLightness(lightness).toColor();
|
||||
}
|
||||
|
||||
/// Obtient une couleur plus sombre
|
||||
static Color darken(Color color, [double amount = 0.1]) {
|
||||
final hsl = HSLColor.fromColor(color);
|
||||
final lightness = (hsl.lightness - amount).clamp(0.0, 1.0);
|
||||
return hsl.withLightness(lightness).toColor();
|
||||
}
|
||||
}
|
||||
23
lib/shared/design_system/tokens/radius_tokens.dart
Normal file
23
lib/shared/design_system/tokens/radius_tokens.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
/// Tokens de rayon pour le design system
|
||||
/// Définit les rayons de bordure standardisés de l'application
|
||||
library radius_tokens;
|
||||
|
||||
/// Tokens de rayon
|
||||
class RadiusTokens {
|
||||
RadiusTokens._();
|
||||
|
||||
/// Small - 4px
|
||||
static const double sm = 4.0;
|
||||
|
||||
/// Medium - 8px
|
||||
static const double md = 8.0;
|
||||
|
||||
/// Large - 12px
|
||||
static const double lg = 12.0;
|
||||
|
||||
/// Extra large - 16px
|
||||
static const double xl = 16.0;
|
||||
|
||||
/// Round - 50px
|
||||
static const double round = 50.0;
|
||||
}
|
||||
150
lib/shared/design_system/tokens/shadow_tokens.dart
Normal file
150
lib/shared/design_system/tokens/shadow_tokens.dart
Normal 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 = [
|
||||
const BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 1),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre petite - Pour cards et boutons
|
||||
static final List<BoxShadow> sm = [
|
||||
const BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre moyenne - Pour cards importantes
|
||||
static final List<BoxShadow> md = [
|
||||
const BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre large - Pour modals et dialogs
|
||||
static final List<BoxShadow> lg = [
|
||||
const BoxShadow(
|
||||
color: ColorTokens.shadowMedium,
|
||||
blurRadius: 16,
|
||||
offset: Offset(0, 6),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre très large - Pour éléments flottants
|
||||
static final List<BoxShadow> xl = [
|
||||
const BoxShadow(
|
||||
color: ColorTokens.shadowMedium,
|
||||
blurRadius: 24,
|
||||
offset: Offset(0, 8),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre extra large - Pour éléments héroïques
|
||||
static final List<BoxShadow> xxl = [
|
||||
const BoxShadow(
|
||||
color: ColorTokens.shadowHigh,
|
||||
blurRadius: 32,
|
||||
offset: 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 = [
|
||||
const BoxShadow(
|
||||
color: ColorTokens.shadow,
|
||||
blurRadius: 4,
|
||||
offset: 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 = [];
|
||||
}
|
||||
|
||||
194
lib/shared/design_system/tokens/spacing_tokens.dart
Normal file
194
lib/shared/design_system/tokens/spacing_tokens.dart
Normal file
@@ -0,0 +1,194 @@
|
||||
/// Design Tokens - Espacements
|
||||
///
|
||||
/// Système d'espacement cohérent basé sur une grille de 4px
|
||||
/// Optimisé pour la lisibilité et l'harmonie visuelle
|
||||
library spacing_tokens;
|
||||
|
||||
/// Tokens d'espacement - Système de grille moderne
|
||||
class SpacingTokens {
|
||||
SpacingTokens._();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// ESPACEMENT DE BASE - Grille 4px
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Unité de base (4px) - Fondation du système
|
||||
static const double baseUnit = 4.0;
|
||||
|
||||
/// Espacement minimal (2px) - Détails fins
|
||||
static const double xs = baseUnit * 0.5; // 2px
|
||||
|
||||
/// Espacement très petit (4px) - Éléments adjacents
|
||||
static const double sm = baseUnit * 1; // 4px
|
||||
|
||||
/// Espacement petit (8px) - Espacement interne léger
|
||||
static const double md = baseUnit * 2; // 8px
|
||||
|
||||
/// Espacement moyen (12px) - Espacement standard
|
||||
static const double lg = baseUnit * 3; // 12px
|
||||
|
||||
/// Espacement large (16px) - Séparation de composants
|
||||
static const double xl = baseUnit * 4; // 16px
|
||||
|
||||
/// Espacement très large (20px) - Séparation importante
|
||||
static const double xxl = baseUnit * 5; // 20px
|
||||
|
||||
/// Espacement extra large (24px) - Sections principales
|
||||
static const double xxxl = baseUnit * 6; // 24px
|
||||
|
||||
/// Espacement massif (32px) - Séparation majeure
|
||||
static const double huge = baseUnit * 8; // 32px
|
||||
|
||||
/// Espacement géant (48px) - Espacement héroïque
|
||||
static const double giant = baseUnit * 12; // 48px
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// ESPACEMENTS SPÉCIALISÉS - Composants spécifiques
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Padding des conteneurs
|
||||
static const double containerPaddingSmall = lg; // 12px
|
||||
static const double containerPaddingMedium = xl; // 16px
|
||||
static const double containerPaddingLarge = xxxl; // 24px
|
||||
|
||||
/// Marges des cartes
|
||||
static const double cardMargin = xl; // 16px
|
||||
static const double cardPadding = xl; // 16px
|
||||
static const double cardPaddingLarge = xxxl; // 24px
|
||||
|
||||
/// Espacement des listes
|
||||
static const double listItemSpacing = md; // 8px
|
||||
static const double listSectionSpacing = xxxl; // 24px
|
||||
|
||||
/// Espacement des boutons
|
||||
static const double buttonPaddingHorizontal = xl; // 16px
|
||||
static const double buttonPaddingVertical = lg; // 12px
|
||||
static const double buttonSpacing = md; // 8px
|
||||
|
||||
/// Espacement des formulaires
|
||||
static const double formFieldSpacing = xl; // 16px
|
||||
static const double formSectionSpacing = xxxl; // 24px
|
||||
static const double formPadding = xl; // 16px
|
||||
|
||||
/// Espacement de navigation
|
||||
static const double navigationPadding = xl; // 16px
|
||||
static const double navigationItemSpacing = md; // 8px
|
||||
static const double navigationSectionSpacing = xxxl; // 24px
|
||||
|
||||
/// Espacement des en-têtes
|
||||
static const double headerPadding = xl; // 16px
|
||||
static const double headerHeight = 56.0; // Hauteur standard
|
||||
static const double headerElevation = 4.0; // Élévation
|
||||
|
||||
/// Espacement des onglets
|
||||
static const double tabPadding = xl; // 16px
|
||||
static const double tabHeight = 48.0; // Hauteur standard
|
||||
|
||||
/// Espacement des dialogues
|
||||
static const double dialogPadding = xxxl; // 24px
|
||||
static const double dialogMargin = xl; // 16px
|
||||
|
||||
/// Espacement des snackbars
|
||||
static const double snackbarMargin = xl; // 16px
|
||||
static const double snackbarPadding = xl; // 16px
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// RAYONS DE BORDURE - Système cohérent
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Rayon minimal (2px) - Détails subtils
|
||||
static const double radiusXs = 2.0;
|
||||
|
||||
/// Rayon petit (4px) - Éléments fins
|
||||
static const double radiusSm = 4.0;
|
||||
|
||||
/// Rayon moyen (8px) - Standard
|
||||
static const double radiusMd = 8.0;
|
||||
|
||||
/// Rayon large (12px) - Cartes et composants
|
||||
static const double radiusLg = 12.0;
|
||||
|
||||
/// Rayon très large (16px) - Conteneurs principaux
|
||||
static const double radiusXl = 16.0;
|
||||
|
||||
/// Rayon extra large (20px) - Éléments héroïques
|
||||
static const double radiusXxl = 20.0;
|
||||
|
||||
/// Rayon circulaire (999px) - Boutons ronds
|
||||
static const double radiusCircular = 999.0;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// ÉLÉVATIONS - Système d'ombres
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Élévation minimale
|
||||
static const double elevationXs = 1.0;
|
||||
|
||||
/// Élévation petite
|
||||
static const double elevationSm = 2.0;
|
||||
|
||||
/// Élévation moyenne
|
||||
static const double elevationMd = 4.0;
|
||||
|
||||
/// Élévation large
|
||||
static const double elevationLg = 8.0;
|
||||
|
||||
/// Élévation très large
|
||||
static const double elevationXl = 12.0;
|
||||
|
||||
/// Élévation maximale
|
||||
static const double elevationMax = 24.0;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// DIMENSIONS FIXES - Composants standardisés
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Hauteurs de boutons
|
||||
static const double buttonHeightSmall = 32.0;
|
||||
static const double buttonHeightMedium = 40.0;
|
||||
static const double buttonHeightLarge = 48.0;
|
||||
|
||||
/// Hauteurs d'éléments de liste
|
||||
static const double listItemHeightSmall = 48.0;
|
||||
static const double listItemHeightMedium = 56.0;
|
||||
static const double listItemHeightLarge = 72.0;
|
||||
|
||||
/// Largeurs minimales
|
||||
static const double minTouchTarget = 44.0; // Cible tactile minimale
|
||||
static const double minButtonWidth = 64.0; // Largeur minimale bouton
|
||||
|
||||
/// Largeurs maximales
|
||||
static const double maxContentWidth = 600.0; // Largeur max contenu
|
||||
static const double maxDialogWidth = 400.0; // Largeur max dialogue
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// MÉTHODES UTILITAIRES
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Calcule un espacement basé sur l'unité de base
|
||||
static double spacing(double multiplier) {
|
||||
return baseUnit * multiplier;
|
||||
}
|
||||
|
||||
/// Obtient un espacement responsive basé sur la largeur d'écran
|
||||
static double responsiveSpacing(double screenWidth) {
|
||||
if (screenWidth < 600) {
|
||||
return xl; // Mobile
|
||||
} else if (screenWidth < 1200) {
|
||||
return xxxl; // Tablette
|
||||
} else {
|
||||
return huge; // Desktop
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtient un padding responsive
|
||||
static double responsivePadding(double screenWidth) {
|
||||
if (screenWidth < 600) {
|
||||
return xl; // 16px mobile
|
||||
} else if (screenWidth < 1200) {
|
||||
return xxxl; // 24px tablette
|
||||
} else {
|
||||
return huge; // 32px desktop
|
||||
}
|
||||
}
|
||||
}
|
||||
296
lib/shared/design_system/tokens/typography_tokens.dart
Normal file
296
lib/shared/design_system/tokens/typography_tokens.dart
Normal file
@@ -0,0 +1,296 @@
|
||||
/// Design Tokens - Typographie
|
||||
///
|
||||
/// Système typographique sophistiqué basé sur les tendances 2024-2025
|
||||
/// Hiérarchie claire et lisibilité optimale pour applications professionnelles
|
||||
library typography_tokens;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'color_tokens.dart';
|
||||
|
||||
/// Tokens typographiques - Système de texte moderne
|
||||
class TypographyTokens {
|
||||
TypographyTokens._();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// FAMILLES DE POLICES
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Police principale - Inter (moderne et lisible)
|
||||
static const String primaryFontFamily = 'Inter';
|
||||
|
||||
/// Police secondaire - SF Pro Display (élégante)
|
||||
static const String secondaryFontFamily = 'SF Pro Display';
|
||||
|
||||
/// Police monospace - JetBrains Mono (code et données)
|
||||
static const String monospaceFontFamily = 'JetBrains Mono';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// ÉCHELLE TYPOGRAPHIQUE - Basée sur Material Design 3
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Display - Titres principaux et héros
|
||||
static const TextStyle displayLarge = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 57.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: -0.25,
|
||||
height: 1.12,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle displayMedium = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 45.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.0,
|
||||
height: 1.16,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle displaySmall = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 36.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.0,
|
||||
height: 1.22,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
/// Headline - Titres de sections
|
||||
static const TextStyle headlineLarge = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 32.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.0,
|
||||
height: 1.25,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle headlineMedium = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 28.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.0,
|
||||
height: 1.29,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle headlineSmall = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 24.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.0,
|
||||
height: 1.33,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
/// Title - Titres de composants
|
||||
static const TextStyle titleLarge = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 22.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.0,
|
||||
height: 1.27,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle titleMedium = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.15,
|
||||
height: 1.50,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle titleSmall = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.43,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
/// Label - Étiquettes et boutons
|
||||
static const TextStyle labelLarge = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.43,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle labelMedium = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.5,
|
||||
height: 1.33,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle labelSmall = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 11.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.5,
|
||||
height: 1.45,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
/// Body - Texte de contenu
|
||||
static const TextStyle bodyLarge = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.5,
|
||||
height: 1.50,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle bodyMedium = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.25,
|
||||
height: 1.43,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle bodySmall = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.4,
|
||||
height: 1.33,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// STYLES SPÉCIALISÉS - Interface UnionFlow
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Navigation - Styles pour menu et navigation
|
||||
static const TextStyle navigationLabel = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.43,
|
||||
color: ColorTokens.navigationUnselected,
|
||||
);
|
||||
|
||||
static const TextStyle navigationLabelSelected = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.43,
|
||||
color: ColorTokens.navigationSelected,
|
||||
);
|
||||
|
||||
/// Cartes et composants
|
||||
static const TextStyle cardTitle = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.0,
|
||||
height: 1.33,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle cardSubtitle = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.25,
|
||||
height: 1.43,
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
);
|
||||
|
||||
static const TextStyle cardValue = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 24.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.0,
|
||||
height: 1.25,
|
||||
color: ColorTokens.primary,
|
||||
);
|
||||
|
||||
/// Boutons
|
||||
static const TextStyle buttonLarge = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.25,
|
||||
color: ColorTokens.onPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle buttonMedium = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.29,
|
||||
color: ColorTokens.onPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle buttonSmall = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.5,
|
||||
height: 1.33,
|
||||
color: ColorTokens.onPrimary,
|
||||
);
|
||||
|
||||
/// Formulaires
|
||||
static const TextStyle inputLabel = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.1,
|
||||
height: 1.43,
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
);
|
||||
|
||||
static const TextStyle inputText = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.5,
|
||||
height: 1.50,
|
||||
color: ColorTokens.onSurface,
|
||||
);
|
||||
|
||||
static const TextStyle inputHint = TextStyle(
|
||||
fontFamily: primaryFontFamily,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.5,
|
||||
height: 1.50,
|
||||
color: ColorTokens.onSurfaceVariant,
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// MÉTHODES UTILITAIRES
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Applique une couleur à un style
|
||||
static TextStyle withColor(TextStyle style, Color color) {
|
||||
return style.copyWith(color: color);
|
||||
}
|
||||
|
||||
/// Applique un poids de police
|
||||
static TextStyle withWeight(TextStyle style, FontWeight weight) {
|
||||
return style.copyWith(fontWeight: weight);
|
||||
}
|
||||
|
||||
/// Applique une taille de police
|
||||
static TextStyle withSize(TextStyle style, double size) {
|
||||
return style.copyWith(fontSize: size);
|
||||
}
|
||||
}
|
||||
185
lib/shared/design_system/tokens/unionflow_colors.dart
Normal file
185
lib/shared/design_system/tokens/unionflow_colors.dart
Normal file
@@ -0,0 +1,185 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// UnionFlow Color System - Palette Signature
|
||||
/// Inspirée des valeurs de solidarité, prospérité et modernité africaine
|
||||
class UnionFlowColors {
|
||||
UnionFlowColors._();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// COULEURS PRIMAIRES (Identité UnionFlow)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// Vert UnionFlow - Symbole de croissance et prospérité
|
||||
static const Color unionGreen = Color(0xFF0F6B4F);
|
||||
static const Color unionGreenLight = Color(0xFF1F8A67);
|
||||
static const Color unionGreenPale = Color(0xFFEEF5F2);
|
||||
|
||||
/// Or - Symbole de richesse et communauté
|
||||
static const Color gold = Color(0xFFD4A017);
|
||||
static const Color goldLight = Color(0xFFE8C568);
|
||||
static const Color goldPale = Color(0xFFFFF9E6);
|
||||
|
||||
/// Indigo - Modernité et confiance
|
||||
static const Color indigo = Color(0xFF1E2A44);
|
||||
static const Color indigoLight = Color(0xFF3A4A6B);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// COULEURS SECONDAIRES (Accents culturels)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// Terracotta - Chaleur et tradition
|
||||
static const Color terracotta = Color(0xFFE07A5F);
|
||||
static const Color terracottaLight = Color(0xFFF2AC99);
|
||||
static const Color terracottaPale = Color(0xFFFFF3F0);
|
||||
|
||||
/// Ambre - Énergie et optimisme
|
||||
static const Color amber = Color(0xFFF4A261);
|
||||
static const Color amberLight = Color(0xFFF8C590);
|
||||
|
||||
/// Sable - Neutralité élégante
|
||||
static const Color sand = Color(0xFFE9DCC9);
|
||||
static const Color sandLight = Color(0xFFF5F0E8);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// COULEURS SÉMANTIQUES
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// Succès - Validation, confirmation
|
||||
static const Color success = Color(0xFF22C55E);
|
||||
static const Color successLight = Color(0xFF86EFAC);
|
||||
static const Color successPale = Color(0xFFF0FDF4);
|
||||
|
||||
/// Attention - Avertissements
|
||||
static const Color warning = Color(0xFFF59E0B);
|
||||
static const Color warningLight = Color(0xFFFBBF24);
|
||||
static const Color warningPale = Color(0xFFFEFCE8);
|
||||
|
||||
/// Erreur - Actions négatives
|
||||
static const Color error = Color(0xFFEF4444);
|
||||
static const Color errorLight = Color(0xFFFCA5A5);
|
||||
static const Color errorPale = Color(0xFFFEF2F2);
|
||||
|
||||
/// Info - Informations neutres
|
||||
static const Color info = Color(0xFF3B82F6);
|
||||
static const Color infoLight = Color(0xFF93C5FD);
|
||||
static const Color infoPale = Color(0xFFEFF6FF);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// COULEURS DE SURFACE
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// Background principal
|
||||
static const Color background = Color(0xFFF7F9FA);
|
||||
|
||||
/// Surface des cards
|
||||
static const Color surface = Color(0xFFFFFFFF);
|
||||
|
||||
/// Surface variant (légèrement teintée)
|
||||
static const Color surfaceVariant = Color(0xFFF5F7F8);
|
||||
|
||||
/// Surface avec effet glassmorphism
|
||||
static const Color surfaceGlass = Color(0xFFFAFBFC);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// TEXTE & BORDURES
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// Texte principal
|
||||
static const Color textPrimary = Color(0xFF111827);
|
||||
|
||||
/// Texte secondaire
|
||||
static const Color textSecondary = Color(0xFF6B7280);
|
||||
|
||||
/// Texte tertiaire
|
||||
static const Color textTertiary = Color(0xFF9CA3AF);
|
||||
|
||||
/// Bordures subtiles
|
||||
static const Color border = Color(0xFFE5E7EB);
|
||||
|
||||
/// Bordures moyennes
|
||||
static const Color borderMedium = Color(0xFFD1D5DB);
|
||||
|
||||
/// Bordures fortes
|
||||
static const Color borderStrong = Color(0xFF9CA3AF);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// GRADIENTS SIGNATURE
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// Gradient principal (Vert → Or)
|
||||
static const LinearGradient primaryGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [unionGreen, unionGreenLight],
|
||||
);
|
||||
|
||||
/// Gradient chaleureux (Terracotta → Ambre)
|
||||
static const LinearGradient warmGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [terracotta, amber],
|
||||
);
|
||||
|
||||
/// Gradient or (Gold → Gold Light)
|
||||
static const LinearGradient goldGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [gold, goldLight],
|
||||
);
|
||||
|
||||
/// Gradient subtil pour backgrounds
|
||||
static const LinearGradient subtleGradient = LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFFAFBFC), Color(0xFFF7F9FA)],
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// OMBRES SIGNATURE
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// Ombre douce (cards, buttons)
|
||||
static List<BoxShadow> get softShadow => [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF000000).withOpacity(0.08),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre moyenne (modals, floating elements)
|
||||
static List<BoxShadow> get mediumShadow => [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF000000).withOpacity(0.12),
|
||||
blurRadius: 32,
|
||||
offset: const Offset(0, 12),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre forte (dialogs, overlays)
|
||||
static List<BoxShadow> get strongShadow => [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF000000).withOpacity(0.16),
|
||||
blurRadius: 48,
|
||||
offset: const Offset(0, 16),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre colorée verte (pour CTAs)
|
||||
static List<BoxShadow> get greenGlowShadow => [
|
||||
BoxShadow(
|
||||
color: unionGreen.withOpacity(0.2),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
];
|
||||
|
||||
/// Ombre colorée dorée (pour éléments premium)
|
||||
static List<BoxShadow> get goldGlowShadow => [
|
||||
BoxShadow(
|
||||
color: gold.withOpacity(0.25),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
];
|
||||
}
|
||||
107
lib/shared/design_system/unionflow_design_system.dart
Normal file
107
lib/shared/design_system/unionflow_design_system.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
library unionflow_design_system;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// IMPORTS de base pour le Design System
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'tokens/app_colors.dart';
|
||||
import 'tokens/app_typography.dart';
|
||||
import 'tokens/spacing_tokens.dart';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// EXPORTS - Point d'entrée unique (DRY)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export 'tokens/app_colors.dart';
|
||||
export 'tokens/app_typography.dart';
|
||||
export 'tokens/spacing_tokens.dart';
|
||||
export 'theme/app_theme.dart';
|
||||
export 'components/components.dart';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// COMPATIBILITÉ - Shims pour les anciens tokens (Migration progressive)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Shim de compatibilité pour ColorTokens
|
||||
class ColorTokens {
|
||||
static const Color primary = AppColors.primaryGreen;
|
||||
static const Color primaryContainer = AppColors.lightSurface;
|
||||
static const Color onPrimary = Colors.white;
|
||||
static const Color onPrimaryContainer = AppColors.textPrimaryLight;
|
||||
static const Color secondary = AppColors.brandGreen;
|
||||
static const Color secondaryContainer = AppColors.lightSurface;
|
||||
static const Color onSecondary = Colors.white;
|
||||
static const Color tertiary = AppColors.brandGreenLight;
|
||||
static const Color tertiaryContainer = AppColors.lightSurface;
|
||||
static const Color onTertiary = Colors.white;
|
||||
static const Color surface = AppColors.lightSurface;
|
||||
static const Color surfaceVariant = AppColors.lightSurface;
|
||||
static const Color background = AppColors.lightBackground;
|
||||
static const Color onSurface = AppColors.textPrimaryLight;
|
||||
static const Color onSurfaceVariant = AppColors.textSecondaryLight;
|
||||
static const Color outline = AppColors.lightBorder;
|
||||
static const Color outlineVariant = AppColors.lightBorder;
|
||||
static const Color error = AppColors.error;
|
||||
static const Color onError = Colors.white;
|
||||
static const Color success = AppColors.success;
|
||||
static const Color onSuccess = Colors.white;
|
||||
static const Color info = Color(0xFF2196F3);
|
||||
static const Color warning = Color(0xFFFFC107);
|
||||
static const Color shadow = Color(0x1A000000);
|
||||
|
||||
static const List<Color> primaryGradient = [
|
||||
AppColors.primaryGreen,
|
||||
AppColors.brandGreenLight,
|
||||
];
|
||||
}
|
||||
|
||||
/// Shim de compatibilité pour ShadowTokens
|
||||
class ShadowTokens {
|
||||
static const List<BoxShadow> sm = [
|
||||
BoxShadow(
|
||||
color: Color(0x1A000000),
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
];
|
||||
static const List<BoxShadow> md = [
|
||||
BoxShadow(
|
||||
color: Color(0x26000000),
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
];
|
||||
static const List<BoxShadow> primary = md;
|
||||
}
|
||||
|
||||
/// Shim de compatibilité pour RadiusTokens
|
||||
class RadiusTokens {
|
||||
static const double sm = SpacingTokens.radiusSm;
|
||||
static const double md = SpacingTokens.radiusMd;
|
||||
static const double lg = SpacingTokens.radiusLg;
|
||||
static const double xl = SpacingTokens.radiusXl;
|
||||
static const double circular = SpacingTokens.radiusCircular;
|
||||
static const double round = SpacingTokens.radiusCircular; // Ajouté pour compatibilité
|
||||
}
|
||||
|
||||
/// Shim de compatibilité pour TypographyTokens
|
||||
class TypographyTokens {
|
||||
static const TextStyle displayLarge = AppTypography.headerSmall;
|
||||
static const TextStyle displayMedium = AppTypography.headerSmall;
|
||||
static const TextStyle displaySmall = AppTypography.headerSmall;
|
||||
static const TextStyle headlineLarge = AppTypography.headerSmall;
|
||||
static const TextStyle headlineMedium = AppTypography.headerSmall;
|
||||
static const TextStyle headlineSmall = AppTypography.headerSmall;
|
||||
static const TextStyle titleLarge = AppTypography.headerSmall;
|
||||
static const TextStyle titleMedium = AppTypography.headerSmall;
|
||||
static const TextStyle titleSmall = AppTypography.headerSmall;
|
||||
static const TextStyle bodyLarge = AppTypography.bodyTextSmall;
|
||||
static const TextStyle bodyMedium = AppTypography.bodyTextSmall;
|
||||
static const TextStyle bodySmall = AppTypography.subtitleSmall;
|
||||
static const TextStyle labelLarge = AppTypography.actionText;
|
||||
static const TextStyle labelMedium = AppTypography.badgeText;
|
||||
static const TextStyle labelSmall = AppTypography.badgeText;
|
||||
static const TextStyle buttonLarge = AppTypography.actionText;
|
||||
static const TextStyle cardValue = AppTypography.headerSmall;
|
||||
}
|
||||
28
lib/shared/design_system/unionflow_design_v2.dart
Normal file
28
lib/shared/design_system/unionflow_design_v2.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
/// UnionFlow Design System V2 - Design Signature Original
|
||||
/// Export centralisé de tous les composants et tokens du nouveau design system
|
||||
library unionflow_design_v2;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// TOKENS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export 'tokens/unionflow_colors.dart';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// COMPOSANTS SIGNATURE
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
export 'components/union_balance_card.dart';
|
||||
export 'components/union_progress_card.dart';
|
||||
export 'components/union_action_button.dart';
|
||||
export 'components/union_transaction_tile.dart';
|
||||
export 'components/union_line_chart.dart';
|
||||
export 'components/union_pie_chart.dart';
|
||||
export 'components/union_stat_widget.dart';
|
||||
export 'components/animated_fade_in.dart';
|
||||
export 'components/animated_slide_in.dart';
|
||||
export 'components/union_glass_card.dart';
|
||||
export 'components/african_pattern_background.dart';
|
||||
export 'components/union_unified_account_card.dart';
|
||||
export 'components/union_period_filter.dart';
|
||||
export 'components/union_export_button.dart';
|
||||
export 'components/union_notification_badge.dart';
|
||||
|
||||
Reference in New Issue
Block a user