import 'package:flutter/material.dart'; import 'theme.dart'; /// Unterreiter im Stil der Referenz-UI: der aktive Reiter ist eine helle /// Pille, die übrigen sind nur Text. /// /// Die Pille wechselt weich statt zu springen — so ist sichtbar, dass es /// derselbe Reiter ist, der sich bewegt, und nicht ein neuer Bildschirm. class SubTabs extends StatelessWidget { const SubTabs({ super.key, required this.labels, required this.index, required this.onChanged, this.badges, }); final List labels; final int index; final ValueChanged onChanged; /// Punkt neben dem Reiter-Text, an derselben Position je Eintrag wie /// [labels] — z. B. "hier läuft im Hintergrund etwas". `null` oder `false` /// zeigt nichts. final List? badges; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB( MeloSpace.md, MeloSpace.sm, MeloSpace.md, MeloSpace.xs), child: Row( children: [ for (var i = 0; i < labels.length; i++) Padding( padding: const EdgeInsets.only(right: MeloSpace.sm), child: _Reiter( label: labels[i], aktiv: i == index, badge: badges != null && i < badges!.length && badges![i], onTap: () => onChanged(i), ), ), ], ), ); } } class _Reiter extends StatelessWidget { const _Reiter({ required this.label, required this.aktiv, required this.onTap, this.badge = false, }); final String label; final bool aktiv; final bool badge; final VoidCallback onTap; @override Widget build(BuildContext context) { return Semantics( button: true, selected: aktiv, child: Material( color: Colors.transparent, borderRadius: BorderRadius.circular(MeloRadius.pill), clipBehavior: Clip.antiAlias, child: InkWell( onTap: onTap, child: AnimatedContainer( duration: MeloMotion.ruhig(context, MeloMotion.fast), curve: MeloMotion.curve, // Volle 48 dp hoch: darunter trifft man den Reiter im Gehen nicht // zuverlässig (Material 3, Mindestgröße für Bedienelemente). constraints: const BoxConstraints( minHeight: MeloTheme.minTouchTarget, minWidth: MeloTheme.minTouchTarget, ), alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: MeloSpace.md), decoration: BoxDecoration( color: aktiv ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(MeloRadius.pill), // Der inaktive Reiter bleibt als Fläche erkennbar, ohne sich // vorzudrängen — sonst schweben die Wörter im Nichts. border: aktiv ? null : MeloTheme.hairline, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ AnimatedDefaultTextStyle( duration: MeloMotion.ruhig(context, MeloMotion.fast), curve: MeloMotion.curve, style: TextStyle( color: aktiv ? MeloTheme.black : MeloTheme.text2, fontSize: 15, height: 1.2, fontWeight: aktiv ? FontWeight.w600 : FontWeight.w500, ), child: Text(label), ), if (badge) ...[ const SizedBox(width: MeloSpace.xs), Container( width: 6, height: 6, decoration: const BoxDecoration( color: MeloTheme.red, shape: BoxShape.circle, ), ), ], ], ), ), ), ), ); } }