- lib/presentation : pages legacy (explore/network, notifications) avec BLoC - lib/shared/design_system : UnionFlow Design System v2 (tokens, components) + MD3 tokens + module_colors par feature - lib/shared/widgets : widgets transversaux (core_card, core_shimmer, error_widget, loading_widget, powered_by_lions_dev, etc.) - lib/shared/constants + utils
143 lines
4.2 KiB
Dart
143 lines
4.2 KiB
Dart
/// 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 isDark = Theme.of(context).brightness == Brightness.dark;
|
|
final effectiveIconColor = iconColor ?? AppColors.primary;
|
|
final effectiveIconBgColor = iconBackgroundColor ??
|
|
effectiveIconColor.withOpacity(0.1);
|
|
|
|
return Card(
|
|
elevation: SpacingTokens.elevationSm,
|
|
shadowColor: AppColors.borderDark.withOpacity(0.1),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
|
),
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(SpacingTokens.lg),
|
|
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.sm),
|
|
decoration: BoxDecoration(
|
|
color: effectiveIconBgColor,
|
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: effectiveIconColor,
|
|
size: 18,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
// Flèche si cliquable
|
|
if (onTap != null)
|
|
Icon(
|
|
Icons.arrow_forward_ios,
|
|
size: 16,
|
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: SpacingTokens.md),
|
|
|
|
// Titre
|
|
Text(
|
|
title,
|
|
style: AppTypography.badgeText.copyWith(
|
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: SpacingTokens.sm),
|
|
|
|
// Valeur
|
|
Text(
|
|
value,
|
|
style: AppTypography.headerSmall.copyWith(
|
|
color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary,
|
|
),
|
|
),
|
|
|
|
// Sous-titre optionnel
|
|
if (subtitle != null) ...[
|
|
const SizedBox(height: SpacingTokens.sm),
|
|
Text(
|
|
subtitle!,
|
|
style: AppTypography.subtitleSmall.copyWith(
|
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|