diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e3545a08..6500573db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [next] - fix: Add missing properties (`closeIconSize`, `closeButtonStyle`) in `debugFillProperties` and `InfoBarThemeData.merge` ([#1128](https://github.com/bdlukaa/fluent_ui/issues/1128) +- feat: Add `TabView.reservedStripWidth`, which adds a minimum empty area between the tabs and the tab view footer ([#1106](https://github.com/bdlukaa/fluent_ui/issues/1106)) ## 4.9.2 diff --git a/example/lib/screens/navigation/tab_view.dart b/example/lib/screens/navigation/tab_view.dart index 3ac588a52..02b17d123 100644 --- a/example/lib/screens/navigation/tab_view.dart +++ b/example/lib/screens/navigation/tab_view.dart @@ -233,6 +233,7 @@ TabView( height: 400, child: TabView( tabs: tabs!, + reservedStripWidth: 100, currentIndex: currentIndex, onChanged: (index) => setState(() => currentIndex = index), tabWidthBehavior: tabWidthBehavior, diff --git a/lib/src/controls/navigation/tab_view.dart b/lib/src/controls/navigation/tab_view.dart index bef5b332c..a570c25b7 100644 --- a/lib/src/controls/navigation/tab_view.dart +++ b/lib/src/controls/navigation/tab_view.dart @@ -73,6 +73,7 @@ class TabView extends StatefulWidget { this.tabWidthBehavior = TabWidthBehavior.equal, this.header, this.footer, + this.reservedStripWidth, this.stripBuilder, this.closeDelayDuration = const Duration(seconds: 1), }); @@ -170,6 +171,19 @@ class TabView extends StatefulWidget { /// Usually a [Text] widget. final Widget? footer; + /// The minimum width reserved at the end of the tab strip. + /// + /// This reserved space ensures a consistent drag area for window manipulation + /// (e.g., dragging, resizing) even when many tabs are present. This is particularly + /// crucial when `TabView` is used in a title bar. + /// + /// When using TabView in a title bar, this space ensures minimum drag area even + /// when many tabs are present. This is critical for window manipulation (dragging, etc) + /// as it guarantees a consistent drag target regardless of tab count. + /// + /// If `null`, no reserved width is enforced. + final double? reservedStripWidth; + /// The builder for the strip that contains the tabs. final Widget Function(BuildContext context, Widget strip)? stripBuilder; @@ -244,7 +258,8 @@ class TabView extends StatefulWidget { defaultValue: const Duration(seconds: 1), )) ..add(DoubleProperty('minTabWidth', minTabWidth, defaultValue: 80.0)) - ..add(DoubleProperty('maxTabWidth', maxTabWidth, defaultValue: 240.0)); + ..add(DoubleProperty('maxTabWidth', maxTabWidth, defaultValue: 240.0)) + ..add(DoubleProperty('minFooterWidth', reservedStripWidth)); } } @@ -476,10 +491,11 @@ class _TabViewState extends State { 'You can only create a TabView in a box with defined width', ); - preferredTabWidth = - ((width - (widget.showNewButton ? _kButtonWidth : 0)) / - widget.tabs.length) - .clamp(widget.minTabWidth, widget.maxTabWidth); + preferredTabWidth = ((width - + (widget.showNewButton ? _kButtonWidth : 0) - + (widget.reservedStripWidth ?? 0)) / + widget.tabs.length) + .clamp(widget.minTabWidth, widget.maxTabWidth); final Widget listView = Listener( onPointerSignal: (PointerSignalEvent e) { @@ -583,18 +599,22 @@ class _TabViewState extends State { } final strip = Row(children: [ + // scroll buttons if needed if (showScrollButtons) direction == TextDirection.ltr ? backwardButton() : forwardButton(), + // tabs area (flexible/expanded) if (scrollable) Expanded(child: listView) else Flexible(child: listView), + // scroll buttons if needed if (showScrollButtons) direction == TextDirection.ltr ? forwardButton() : backwardButton(), + // new tab button if (widget.showNewButton) Padding( padding: const EdgeInsetsDirectional.only( @@ -624,6 +644,9 @@ class _TabViewState extends State { localizations.newTabLabel, ), ), + // reserved strip width + if (widget.reservedStripWidth != null) + SizedBox(width: widget.reservedStripWidth), ]); if (widget.stripBuilder != null) {