fix side rail overflows
This commit is contained in:
+17
-36
@@ -481,42 +481,21 @@ class BrowserTabBar extends HookConsumerWidget {
|
||||
axis: switcherAxis,
|
||||
),
|
||||
// History fallback only on the MRU row, so empty-state history
|
||||
// chips don't show twice. On the rail the two rows stack as two
|
||||
// equal-height vertical lists.
|
||||
TabBarStackingMode.twoLevel =>
|
||||
isVertical
|
||||
? Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: QuickTabSwitcher(
|
||||
quickTabSwitcherMode:
|
||||
QuickTabSwitcherMode.containerTabs,
|
||||
enableHistoryFallback: false,
|
||||
axis: switcherAxis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: QuickTabSwitcher(
|
||||
quickTabSwitcherMode:
|
||||
QuickTabSwitcherMode.lastUsedTabs,
|
||||
axis: switcherAxis,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
QuickTabSwitcher(
|
||||
quickTabSwitcherMode:
|
||||
QuickTabSwitcherMode.containerTabs,
|
||||
enableHistoryFallback: false,
|
||||
),
|
||||
QuickTabSwitcher(
|
||||
quickTabSwitcherMode: QuickTabSwitcherMode.lastUsedTabs,
|
||||
),
|
||||
],
|
||||
),
|
||||
// chips don't show twice. Only reached on the horizontal bar:
|
||||
// effectiveTabBarStackingMode() degrades twoLevel to containerTabs
|
||||
// on the narrow vertical rail, where two stacked rows don't fit.
|
||||
TabBarStackingMode.twoLevel => const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
QuickTabSwitcher(
|
||||
quickTabSwitcherMode: QuickTabSwitcherMode.containerTabs,
|
||||
enableHistoryFallback: false,
|
||||
),
|
||||
QuickTabSwitcher(
|
||||
quickTabSwitcherMode: QuickTabSwitcherMode.lastUsedTabs,
|
||||
),
|
||||
],
|
||||
),
|
||||
},
|
||||
),
|
||||
contextualToolbar: ContextualToolbar(
|
||||
@@ -1228,6 +1207,7 @@ class QuickTabSwitcherView extends StatelessWidget {
|
||||
context,
|
||||
showTitles: showTitles,
|
||||
hierarchyGlyphs: hierarchyGlyphs,
|
||||
isVertical: _isVertical,
|
||||
canDelete: _canShowCloseButton,
|
||||
);
|
||||
}
|
||||
@@ -1245,6 +1225,7 @@ class QuickTabSwitcherView extends StatelessWidget {
|
||||
showIsolatedTabUi: showIsolatedTabUi,
|
||||
hierarchyGlyphs: hierarchyGlyphs,
|
||||
titleMaxWidth: titleMaxWidth,
|
||||
isVertical: _isVertical,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -162,6 +162,7 @@ class AccordionQuickTabSwitcher extends HookConsumerWidget {
|
||||
context,
|
||||
showTitles: showTitles,
|
||||
hierarchyGlyphs: hierarchyGlyphs,
|
||||
isVertical: isVertical,
|
||||
// The tray is painted in the container color, so the active tab's normal
|
||||
// (transparent) selected border would blend in; give it a thicker border
|
||||
// in the container's outline color instead.
|
||||
@@ -201,6 +202,7 @@ class AccordionQuickTabSwitcher extends HookConsumerWidget {
|
||||
showIsolatedTabUi: showIsolatedTabUi,
|
||||
hierarchyGlyphs: hierarchyGlyphs,
|
||||
titleMaxWidth: titleMaxWidth,
|
||||
isVertical: isVertical,
|
||||
),
|
||||
// Spacing inside the expanded group is owned by the surrounding tray
|
||||
// slice so the slices abut into one continuous background.
|
||||
@@ -527,7 +529,9 @@ class _AccordionHeaderChip extends StatelessWidget {
|
||||
if (iconAvatar != null) iconAvatar,
|
||||
if (countBadge != null) ...[
|
||||
if (iconAvatar != null) const SizedBox(height: 4),
|
||||
countBadge,
|
||||
// Multi-digit counts can exceed the narrow rail's fixed 48px chip
|
||||
// width; scale the badge down to fit instead of overflowing.
|
||||
FittedBox(fit: BoxFit.scaleDown, child: countBadge),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
+77
-3
@@ -146,6 +146,10 @@ buildQuickTabSwitcherChipDecoration(
|
||||
BuildContext context, {
|
||||
required bool showTitles,
|
||||
required int hierarchyGlyphs,
|
||||
// On the vertical rail nesting is shown as a corner badge on the favicon (not
|
||||
// a leading pill), so a nested chip carries no extra inline width and can use
|
||||
// the same zero label padding as a leaf chip instead of overflowing.
|
||||
bool isVertical = false,
|
||||
bool Function(QuickTabSwitcherItem item)? canDelete,
|
||||
// When true, the selected item gets a thicker border in its container's
|
||||
// outline color (instead of the usual transparent selected border). The
|
||||
@@ -195,7 +199,7 @@ buildQuickTabSwitcherChipDecoration(
|
||||
!item.isHistory &&
|
||||
!item.isPinned &&
|
||||
!item.isSandbox &&
|
||||
(item.depth == 0 || hierarchyGlyphs == 0) &&
|
||||
(item.depth == 0 || hierarchyGlyphs == 0 || isVertical) &&
|
||||
item.tabMode is! PrivateTabMode &&
|
||||
item.tabMode is! IsolatedTabMode)
|
||||
? EdgeInsets.zero
|
||||
@@ -213,13 +217,22 @@ Widget buildQuickTabSwitcherChipLabel(
|
||||
required bool showIsolatedTabUi,
|
||||
required int hierarchyGlyphs,
|
||||
required double titleMaxWidth,
|
||||
// On the narrow vertical rail the leading depth pill has no room beside the
|
||||
// favicon (it clips the icon), so nesting is shown as a compact corner badge
|
||||
// overlaid on the favicon instead.
|
||||
bool isVertical = false,
|
||||
}) {
|
||||
final appColors = AppColors.of(context);
|
||||
final hasTitle = item.isHistory || showTitles;
|
||||
final isNested = item.depth > 0 && hierarchyGlyphs > 0;
|
||||
final showInlineDepth = isNested && !isVertical;
|
||||
final avatar = (isNested && isVertical)
|
||||
? _RailDepthAvatar(depth: item.depth, child: item.avatar)
|
||||
: item.avatar;
|
||||
final row = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (item.depth > 0 && hierarchyGlyphs > 0)
|
||||
if (showInlineDepth)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6.0),
|
||||
child: TabDepthIndicator(
|
||||
@@ -232,7 +245,7 @@ Widget buildQuickTabSwitcherChipLabel(
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: hasTitle ? 6.0 : 0.0),
|
||||
child: item.avatar,
|
||||
child: avatar,
|
||||
),
|
||||
if (hasTitle)
|
||||
ConstrainedBox(
|
||||
@@ -305,6 +318,67 @@ Widget buildQuickTabSwitcherChipLabel(
|
||||
row;
|
||||
}
|
||||
|
||||
/// Favicon with a compact nesting badge overlaid on its bottom-right corner,
|
||||
/// used on the vertical side rail where the inline [TabDepthIndicator] pill
|
||||
/// would clip the favicon. The badge shows a subdirectory-arrow glyph for a
|
||||
/// direct child and the depth number for deeper nesting.
|
||||
class _RailDepthAvatar extends StatelessWidget {
|
||||
final int depth;
|
||||
final Widget child;
|
||||
|
||||
const _RailDepthAvatar({required this.depth, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
// Keep the exact 20px footprint of a plain favicon so a nested chip stays
|
||||
// the same width as a leaf chip on the narrow rail; the badge is anchored
|
||||
// inside the horizontal bounds (only the vertical corner is allowed to
|
||||
// bleed, where there's ample room) so it never widens the chip.
|
||||
return SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Positioned.fill(child: child),
|
||||
Positioned(
|
||||
right: -3,
|
||||
bottom: -5,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(minWidth: 12, minHeight: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 1.0),
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.secondaryContainer,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
|
||||
border: Border.all(color: scheme.surfaceContainer, width: 1.0),
|
||||
),
|
||||
child: Center(
|
||||
child: depth > 1
|
||||
? Text(
|
||||
'$depth',
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
height: 1.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: scheme.onSecondaryContainer,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
MdiIcons.subdirectoryArrowRight,
|
||||
size: 10,
|
||||
color: scheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// FilterChip matching [SelectableChips]' visual contract, used by render
|
||||
/// paths that can't go through [SelectableChips] itself (the reorderable
|
||||
/// list and the accordion view). Stateless wrapper so a parent
|
||||
|
||||
+14
-10
@@ -400,13 +400,13 @@ class _TabBarStackingModeSection extends HookConsumerWidget {
|
||||
title: Text('Recently Used Tabs'),
|
||||
subtitle: Text('Recently used tabs across all containers'),
|
||||
),
|
||||
if (settings.showContainerUi) ...const [
|
||||
RadioListTile.adaptive(
|
||||
if (settings.showContainerUi) ...[
|
||||
const RadioListTile.adaptive(
|
||||
value: TabBarStackingMode.containerTabs,
|
||||
title: Text('Container Tabs'),
|
||||
subtitle: Text('Ordered tabs of the selected container'),
|
||||
),
|
||||
RadioListTile.adaptive(
|
||||
const RadioListTile.adaptive(
|
||||
value: TabBarStackingMode.accordion,
|
||||
title: Text('Accordion'),
|
||||
subtitle: Text(
|
||||
@@ -414,14 +414,18 @@ class _TabBarStackingModeSection extends HookConsumerWidget {
|
||||
"container's tabs expanded inline",
|
||||
),
|
||||
),
|
||||
RadioListTile.adaptive(
|
||||
value: TabBarStackingMode.twoLevel,
|
||||
title: Text('Two Rows'),
|
||||
subtitle: Text(
|
||||
'Tabs of the selected container on top, recently used '
|
||||
'tabs below',
|
||||
// Two stacked rows don't fit the narrow vertical side rail,
|
||||
// where the mode degrades to Container Tabs; hide the option
|
||||
// for left/right positions to avoid a no-op choice.
|
||||
if (!settings.tabBarPosition.isVertical)
|
||||
const RadioListTile.adaptive(
|
||||
value: TabBarStackingMode.twoLevel,
|
||||
title: Text('Two Rows'),
|
||||
subtitle: Text(
|
||||
'Tabs of the selected container on top, recently used '
|
||||
'tabs below',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const RadioListTile.adaptive(
|
||||
value: TabBarStackingMode.disabled,
|
||||
|
||||
@@ -511,17 +511,26 @@ class GeneralSettings with FastEquatable {
|
||||
}
|
||||
|
||||
/// Container-dependent stacking modes degrade to a single recently-used
|
||||
/// row when the container UI is disabled.
|
||||
/// row when the container UI is disabled. Two-level stacking additionally
|
||||
/// degrades to the single container-tabs row on the narrow vertical rail,
|
||||
/// where two stacked chip lists have no room.
|
||||
TabBarStackingMode effectiveTabBarStackingMode() {
|
||||
var mode = tabBarStackingMode;
|
||||
|
||||
if (tabBarPosition.isVertical && mode == TabBarStackingMode.twoLevel) {
|
||||
mode = TabBarStackingMode.containerTabs;
|
||||
}
|
||||
|
||||
if (!showContainerUi &&
|
||||
const {
|
||||
TabBarStackingMode.containerTabs,
|
||||
TabBarStackingMode.accordion,
|
||||
TabBarStackingMode.twoLevel,
|
||||
}.contains(tabBarStackingMode)) {
|
||||
}.contains(mode)) {
|
||||
return TabBarStackingMode.lastUsedTabs;
|
||||
}
|
||||
return tabBarStackingMode;
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user