From 487c7b87f686afa483dede325e81417aa4a3f8ff Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Fri, 22 May 2026 02:21:06 +0200 Subject: [PATCH] fix(nav): hide active sidebar background during focus --- lib/widgets/side_navigation_rail.dart | 13 ++-- test/widgets/side_navigation_rail_test.dart | 71 +++++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/lib/widgets/side_navigation_rail.dart b/lib/widgets/side_navigation_rail.dart index f354798c..5ecfaffa 100644 --- a/lib/widgets/side_navigation_rail.dart +++ b/lib/widgets/side_navigation_rail.dart @@ -60,6 +60,7 @@ class NavigationRailItem extends StatelessWidget { final BorderRadius borderRadius; final double iconSize; final double horizontalPadding; + final bool suppressSelectedBackground; /// Called when RIGHT arrow is pressed to navigate to content area. final VoidCallback? onNavigateRight; @@ -79,12 +80,14 @@ class NavigationRailItem extends StatelessWidget { this.borderRadius = const BorderRadius.all(Radius.circular(12)), this.iconSize = 22, this.horizontalPadding = 17, + this.suppressSelectedBackground = false, this.onNavigateRight, }); @override Widget build(BuildContext context) { final t = tokens(context); + final showSelectedBackground = isSelected && !suppressSelectedBackground; return Focus( focusNode: focusNode, @@ -111,9 +114,8 @@ class NavigationRailItem extends StatelessWidget { decoration: BoxDecoration( color: () { if (isCollapsed) return isFocused ? t.text.withValues(alpha: 0.12) : null; - if (isSelected && isFocused) return t.text.withValues(alpha: 0.15); - if (isSelected) return t.text.withValues(alpha: 0.1); - if (isFocused) return t.text.withValues(alpha: 0.12); + if (isFocused) return t.text.withValues(alpha: showSelectedBackground ? 0.15 : 0.12); + if (showSelectedBackground) return t.text.withValues(alpha: 0.1); return null; }(), borderRadius: borderRadius, @@ -792,6 +794,7 @@ class SideNavigationRailState extends State with MountedSetS focusNode: focusNode, autofocus: autofocus, horizontalPadding: itemHorizontalPadding, + suppressSelectedBackground: widget.isSidebarFocused, onNavigateRight: widget.onNavigateToContent, ); } @@ -858,6 +861,7 @@ class SideNavigationRailState extends State with MountedSetS final isLoading = librariesProvider.isLoading; final isLibrariesSelected = widget.selectedTab == NavigationTabId.libraries && widget.selectedLibraryKey == null; final isLibrariesFocused = _focusTracker.isFocused(_kLibraries); + final showLibrariesSelectedBackground = isLibrariesSelected && !widget.isSidebarFocused; final allEmpty = visibleRows.isEmpty && hiddenLibraryCount == 0; return Column( @@ -894,7 +898,7 @@ class SideNavigationRailState extends State with MountedSetS decoration: BoxDecoration( color: () { if (isCollapsed) return isLibrariesFocused ? t.text.withValues(alpha: 0.08) : null; - if (isLibrariesSelected) return t.text.withValues(alpha: 0.1); + if (showLibrariesSelectedBackground) return t.text.withValues(alpha: 0.1); if (isLibrariesFocused) return t.text.withValues(alpha: 0.08); return null; }(), @@ -1190,6 +1194,7 @@ class SideNavigationRailState extends State with MountedSetS focusNode: focusNode, borderRadius: BorderRadius.circular(tokens(context).radiusSm), iconSize: 18, + suppressSelectedBackground: widget.isSidebarFocused, onNavigateRight: widget.onNavigateToContent, ), ); diff --git a/test/widgets/side_navigation_rail_test.dart b/test/widgets/side_navigation_rail_test.dart index 0b7be0db..90c5a173 100644 --- a/test/widgets/side_navigation_rail_test.dart +++ b/test/widgets/side_navigation_rail_test.dart @@ -58,6 +58,58 @@ Future _press(WidgetTester tester, LogicalKeyboardKey key) async { await tester.pumpAndSettle(); } +BoxDecoration? _railItemDecoration(WidgetTester tester, Finder item) { + return tester.widget(find.descendant(of: item, matching: find.byType(Container)).first).decoration + as BoxDecoration?; +} + +Future _pumpBasicRail( + WidgetTester tester, { + GlobalKey? sideNavKey, + bool isSidebarFocused = false, + bool alwaysExpanded = false, +}) async { + await SettingsService.getInstance(); + + final librariesProvider = LibrariesProvider(); + addTearDown(librariesProvider.dispose); + + final hiddenLibrariesProvider = HiddenLibrariesProvider(); + await hiddenLibrariesProvider.ensureInitialized(); + addTearDown(hiddenLibrariesProvider.dispose); + + final manager = MultiServerManager(); + final aggregation = DataAggregationService(manager); + final multiServerProvider = MultiServerProvider(manager, aggregation); + addTearDown(multiServerProvider.dispose); + + await tester.pumpWidget( + TranslationProvider( + child: MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: librariesProvider), + ChangeNotifierProvider.value(value: hiddenLibrariesProvider), + ChangeNotifierProvider.value(value: multiServerProvider), + ], + child: MaterialApp( + theme: ThemeData(extensions: const [_testTokens]), + home: Scaffold( + body: SideNavigationRail( + key: sideNavKey, + selectedTab: NavigationTabId.discover, + isSidebarFocused: isSidebarFocused, + alwaysExpanded: alwaysExpanded, + onDestinationSelected: (_) {}, + onLibrarySelected: (_) {}, + ), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); +} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -177,6 +229,25 @@ void main() { expect(surfaceOpacity.opacity, 0.0); }); + testWidgets('expanded rail keeps selected background outside sidebar keyboard focus', (tester) async { + await _pumpBasicRail(tester, alwaysExpanded: true); + + final selectedItem = find.byType(NavigationRailItem).first; + expect(_railItemDecoration(tester, selectedItem)?.color, _testTokens.text.withValues(alpha: 0.1)); + }); + + testWidgets('D-pad sidebar focus hides selected item background after focus moves', (tester) async { + final sideNavKey = GlobalKey(); + await _pumpBasicRail(tester, sideNavKey: sideNavKey, isSidebarFocused: true, alwaysExpanded: true); + + sideNavKey.currentState!.focusActiveItem(); + await tester.pumpAndSettle(); + await _press(tester, LogicalKeyboardKey.arrowDown); + + final selectedItem = find.byType(NavigationRailItem).first; + expect(_railItemDecoration(tester, selectedItem)?.color, isNull); + }); + testWidgets('reports interaction expansion for shell content push', (tester) async { await SettingsService.getInstance();