import 'package:flutter/material.dart'; import '../theme/m3_motion.dart'; import 'app_breakpoints.dart'; /// Standardized M3 Expressive page header with animated entrance. /// /// Replaces the ad-hoc `Padding(Align(Text(...)))` pattern found across /// screens. Provides: /// * `headlineSmall` typography — correct M3 size for a top-level page title. /// * [M3FadeSlideIn] entrance animation — 400 ms with emphasized easing. /// * Optional [subtitle] in `bodyMedium` / `onSurfaceVariant`. /// * Optional [actions] row rendered at the trailing end. /// * Responsive alignment: left-aligned on desktop (≥600 dp), centered on /// mobile to match the existing navigation-bar-centric layout. class AppPageHeader extends StatelessWidget { const AppPageHeader({ super.key, required this.title, this.subtitle, this.actions, this.padding, }); final String title; final String? subtitle; /// Optional trailing action widgets (e.g. filter chip, icon button). final List? actions; /// Vertical padding around the header. Horizontal padding is handled by /// the parent [ResponsiveBody] so only top/bottom should be set here. /// When null, defaults to responsive bottom padding (24dp desktop / 12dp mobile). final EdgeInsetsGeometry? padding; @override Widget build(BuildContext context) { final tt = Theme.of(context).textTheme; final cs = Theme.of(context).colorScheme; final isMobile = AppBreakpoints.isMobile(context); final titleWidget = Text( title, style: (isMobile ? tt.titleLarge : tt.headlineSmall)?.copyWith( fontWeight: FontWeight.w700, letterSpacing: -0.3, color: cs.onSurface, ), textAlign: TextAlign.center, ); final subtitleWidget = (subtitle == null || isMobile) ? null : Text( subtitle!, style: tt.bodyMedium?.copyWith(color: cs.onSurfaceVariant), textAlign: TextAlign.center, ); final hasActions = actions != null && actions!.isNotEmpty; Widget content; if (!hasActions) { content = Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ titleWidget, if (subtitleWidget != null) ...[ const SizedBox(height: 4), subtitleWidget, ], ], ); } else { // With actions — centered title/subtitle, actions sit to the right. content = Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ titleWidget, if (subtitleWidget != null) ...[ const SizedBox(height: 4), subtitleWidget, ], ], ), ), const SizedBox(width: 8), Row(mainAxisSize: MainAxisSize.min, children: actions!), ], ); } final effectivePadding = padding ?? EdgeInsets.only( top: isMobile ? 12 : 20, bottom: AppBreakpoints.isDesktop(context) ? 24 : 12, ); return M3FadeSlideIn( duration: M3Motion.standard, child: Padding(padding: effectivePadding, child: content), ); } }