import 'package:flutter/material.dart'; import '../../about/internal/application/TextType.dart'; import '../../utils/Colors.dart'; import '../text/Text.dart'; class NavItemData { final IconData icon; final String label; final int? badge; final VoidCallback onTap; final bool selected; const NavItemData({ required this.icon, required this.label, required this.onTap, this.badge, this.selected = false, }); } /// Left navigation rail / sidebar. [expanded] shows labels; otherwise a /// compact icon rail. class SideNav extends StatelessWidget { final bool expanded; final List items; final ThemeMode themeMode; final Future Function() onCycleTheme; final VoidCallback onNewServer; const SideNav({ super.key, required this.expanded, required this.items, required this.themeMode, required this.onCycleTheme, required this.onNewServer, }); (IconData, String) get _themeIcon => switch (themeMode) { ThemeMode.dark => (Icons.dark_mode, 'Dark'), ThemeMode.light => (Icons.light_mode, 'Light'), ThemeMode.system => (Icons.brightness_auto, 'System'), }; @override Widget build(BuildContext context) { final jp = context.jp; final (themeI, themeLabel) = _themeIcon; return Container( width: expanded ? 236 : 78, decoration: BoxDecoration( color: jp.surface, border: Border(right: BorderSide(color: jp.border)), ), child: SafeArea( right: false, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: EdgeInsets.fromLTRB(expanded ? 20 : 0, 20, 0, 20), child: Row( mainAxisAlignment: expanded ? MainAxisAlignment.start : MainAxisAlignment.center, children: [ Container( width: 38, height: 38, decoration: BoxDecoration( gradient: PeachTheme.brandGradient, borderRadius: BorderRadius.circular(11), boxShadow: PeachTheme.glow( PeachTheme.seed, opacity: 0.5, blur: 18, ), ), child: const Icon( Icons.rocket_launch, color: Colors.white, size: 20, ), ), if (expanded) ...[ const SizedBox(width: 12), text( 'Peach', 20, TextType.Bold, color: jp.text, weight: FontWeight.w800, ), ], ], ), ), Expanded( child: ListView( padding: EdgeInsets.symmetric(horizontal: expanded ? 12 : 10), children: [for (final it in items) _navItem(context, it)], ), ), Padding( padding: EdgeInsets.symmetric(horizontal: expanded ? 12 : 10), child: Column( children: [ _footerButton( context, themeI, themeLabel, (_) => onCycleTheme(), ), const SizedBox(height: 6), _newServerButton(context), const SizedBox(height: 14), ], ), ), ], ), ), ); } Widget _navItem(BuildContext context, NavItemData it) { final jp = context.jp; final inner = Container( padding: EdgeInsets.symmetric( horizontal: expanded ? 14 : 0, vertical: 12, ), decoration: BoxDecoration( color: it.selected ? PeachTheme.seed.withValues(alpha: 0.13) : Colors.transparent, borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: expanded ? MainAxisAlignment.start : MainAxisAlignment.center, children: [ Icon( it.icon, size: 20, color: it.selected ? PeachTheme.seed : jp.textDim, ), if (expanded) ...[ const SizedBox(width: 12), Expanded( child: text( it.label, 14, TextType.Regular, color: it.selected ? jp.text : jp.textDim, overflow: TextOverflow.ellipsis, weight: it.selected ? FontWeight.w700 : FontWeight.w600, ), ), if (it.badge != null && it.badge! > 0) Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 1), decoration: BoxDecoration( color: jp.surface2, borderRadius: BorderRadius.circular(20), ), child: text( '${it.badge}', 11, TextType.Regular, color: jp.textDim, weight: FontWeight.w700, ), ), ], ], ), ); // A premium left accent bar marks the selected item. final content = Container( margin: const EdgeInsets.only(bottom: 6), child: Row( children: [ AnimatedContainer( duration: const Duration(milliseconds: 160), width: 3, height: it.selected ? 22 : 0, margin: EdgeInsets.only(right: expanded ? 8 : 0), decoration: BoxDecoration( color: PeachTheme.seed, borderRadius: BorderRadius.circular(3), ), ), Expanded(child: inner), ], ), ); return InkWell( borderRadius: BorderRadius.circular(12), onTap: it.onTap, child: expanded ? content : Tooltip(message: it.label, child: content), ); } Widget _footerButton( BuildContext context, IconData icon, String label, void Function(BuildContext) onTap, ) { final jp = context.jp; final w = InkWell( borderRadius: BorderRadius.circular(12), onTap: () => onTap(context), child: Container( padding: EdgeInsets.symmetric( horizontal: expanded ? 14 : 0, vertical: 12, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: jp.border), ), child: Row( mainAxisAlignment: expanded ? MainAxisAlignment.start : MainAxisAlignment.center, children: [ Icon(icon, size: 19, color: jp.textDim), if (expanded) ...[ const SizedBox(width: 12), text( 'Theme: $label', 13, TextType.Regular, color: jp.textDim, weight: FontWeight.w400, ), ], ], ), ), ); return expanded ? w : Tooltip(message: 'Theme: $label', child: w); } Widget _newServerButton(BuildContext context) { if (!expanded) { return Tooltip( message: 'New server', child: InkWell( borderRadius: BorderRadius.circular(12), onTap: onNewServer, child: Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( gradient: PeachTheme.brandGradient, borderRadius: BorderRadius.circular(12), boxShadow: PeachTheme.glow( PeachTheme.seed, opacity: 0.5, blur: 16, ), ), child: const Icon(Icons.add, color: Colors.white), ), ), ); } return SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: onNewServer, icon: const Icon(Icons.add), label: const Text('New server'), ), ); } }