// widgets/ds/m_hub.dart — 移动 hub 入口组(镜像原型 mobile-atoms .m-hub / .m-section)。 // 组 = 可选分组标题(.m-section) + 圆角组卡;行 = 34×34 圆角图标底(.hic brand50/primary) // + 文案(fs-body) + 右侧 ›(.hchev faint);行间 border-subtle 分隔。 import 'package:flutter/material.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../core/theme/app_dims.g.dart'; import '../../core/theme/context_tokens.dart'; class MHubItem { final IconData icon; final String label; final VoidCallback? onTap; /// 自定义尾部(如角标 / 当前值文案);null → 默认 › 箭头。 final Widget? trailing; const MHubItem({ required this.icon, required this.label, this.onTap, this.trailing, }); } class MHubGroup extends StatelessWidget { /// 分组标题(.m-section);null 不渲染。 final String? label; final List items; const MHubGroup({super.key, this.label, required this.items}); @override Widget build(BuildContext context) { final t = context.tokens; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (label != null) Padding( padding: const EdgeInsets.fromLTRB(2, 16, 2, 8), child: Text(label!, style: TextStyle( fontSize: AppDims.fsSm, fontWeight: FontWeight.w700, letterSpacing: .4, color: t.muted)), ), // Material 自带(InkWell 水波 + golden 直挂无 Scaffold 场景) Material( color: t.surface, clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( side: BorderSide(color: t.border), borderRadius: BorderRadius.circular(AppDims.rLg), ), child: Column(children: [ for (var i = 0; i < items.length; i++) _MHubRow(item: items[i], last: i == items.length - 1), ]), ), ], ); } } class _MHubRow extends StatelessWidget { final MHubItem item; final bool last; const _MHubRow({required this.item, required this.last}); @override Widget build(BuildContext context) { final t = context.tokens; return InkWell( onTap: item.onTap, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( border: last ? null : Border(bottom: BorderSide(color: t.borderSubtle)), ), child: Row(children: [ Container( width: 34, height: 34, decoration: BoxDecoration( color: t.brand50, borderRadius: BorderRadius.circular(AppDims.rMd), ), child: Icon(item.icon, size: 18, color: t.primary), ), const SizedBox(width: 12), Expanded( child: Text(item.label, style: TextStyle(fontSize: AppDims.fsBody, color: t.text)), ), item.trailing ?? Icon(LucideIcons.chevronRight, size: 18, color: t.faint), ]), ), ); } }