import 'package:flutter/material.dart'; import '../../core/theme/design_tokens.dart'; class DetailHeaderActionItem { const DetailHeaderActionItem({ required this.value, required this.label, required this.icon, this.isDestructive = false, }); final T value; final String label; final IconData icon; final bool isDestructive; } class DetailHeaderActionMenu extends StatefulWidget { const DetailHeaderActionMenu({ super.key, required this.items, required this.onSelected, }); final List> items; final ValueChanged onSelected; @override State> createState() => _DetailHeaderActionMenuState(); } class _DetailHeaderActionMenuState extends State> { static const double _buttonSize = AppSpacing.xl * 2; static const double _menuWidth = AppSpacing.xxl * 8; final LayerLink _layerLink = LayerLink(); OverlayEntry? _menuEntry; bool get _isMenuOpen => _menuEntry != null; @override void dispose() { _hideMenu(); super.dispose(); } void _toggleMenu() { if (_isMenuOpen) { _hideMenu(); return; } _showMenu(); } void _showMenu() { final overlay = Overlay.of(context); _menuEntry = OverlayEntry( builder: (context) { return Stack( children: [ Positioned.fill( child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: _hideMenu, child: const SizedBox.expand(), ), ), CompositedTransformFollower( link: _layerLink, showWhenUnlinked: false, offset: const Offset( _buttonSize - _menuWidth, _buttonSize + AppSpacing.sm, ), child: _buildMenuCard(), ), ], ); }, ); overlay.insert(_menuEntry!); setState(() {}); } void _hideMenu() { _menuEntry?.remove(); _menuEntry = null; if (mounted) { setState(() {}); } } void _handleSelect(T value) { _hideMenu(); widget.onSelected(value); } Widget _buildMenuCard() { return Material( color: Colors.transparent, child: Container( width: _menuWidth, padding: const EdgeInsets.symmetric(vertical: AppSpacing.sm), decoration: BoxDecoration( color: AppColors.white, borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all(color: AppColors.borderSecondary), boxShadow: [ BoxShadow( color: AppColors.slate300.withValues(alpha: 0.42), blurRadius: AppSpacing.xl, offset: const Offset(0, AppSpacing.sm), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ for (int i = 0; i < widget.items.length; i++) ...[ _buildMenuItem(widget.items[i]), if (i < widget.items.length - 1) const Padding( padding: EdgeInsets.symmetric(horizontal: AppSpacing.md), child: Divider( height: 1, thickness: 1, color: AppColors.slate100, ), ), ], ], ), ), ); } Widget _buildMenuItem(DetailHeaderActionItem item) { final textColor = item.isDestructive ? AppColors.red500 : AppColors.slate700; final pressedColor = item.isDestructive ? AppColors.feedbackErrorSurface : AppColors.surfaceInfoLight; return SizedBox( height: AppSpacing.xxl * 2, child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.md), splashColor: pressedColor, highlightColor: pressedColor, onTap: () => _handleSelect(item.value), child: Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon( item.icon, size: AppSpacing.lg + AppSpacing.xs, color: textColor, ), const SizedBox(width: AppSpacing.md), Text( item.label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: textColor, ), ), ], ), ), ), ), ); } @override Widget build(BuildContext context) { if (widget.items.isEmpty) { return const SizedBox.shrink(); } return CompositedTransformTarget( link: _layerLink, child: GestureDetector( onTap: _toggleMenu, child: AnimatedContainer( duration: const Duration(milliseconds: 140), width: _buttonSize, height: _buttonSize, decoration: BoxDecoration( color: _isMenuOpen ? AppColors.surfaceInfo : AppColors.surfaceTertiary, borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all( color: _isMenuOpen ? AppColors.borderQuaternary : AppColors.borderTertiary, ), ), child: const Icon( Icons.more_horiz, size: AppSpacing.lg + AppSpacing.xs, color: AppColors.slate600, ), ), ), ); } }