side rail initial

This commit is contained in:
Fabian Freund
2026-07-05 08:43:46 +02:00
parent 7931f66f5a
commit d83487325c
16 changed files with 1197 additions and 275 deletions
@@ -203,6 +203,16 @@ class _TabBarPositionSection extends HookConsumerWidget {
title: Text('Bottom'),
subtitle: Text('Tab bar with auto-hide support'),
),
RadioListTile.adaptive(
value: TabBarPosition.left,
title: Text('Left'),
subtitle: Text('Vertical side rail, swipe to hide'),
),
RadioListTile.adaptive(
value: TabBarPosition.right,
title: Text('Right'),
subtitle: Text('Vertical side rail, swipe to hide'),
),
],
),
),
@@ -72,13 +72,20 @@ class TabBarPreviewHeaderDelegate extends SliverPersistentHeaderDelegate {
double get _headerHeight => compact ? 0.0 : _kHeaderHeight;
@override
double get minExtent =>
_baseHeight + _toolbarHeight + _headerHeight + padding.vertical;
/// Fixed preview height for the vertical rail (the rail flows along the
/// height, so it can't be derived from stacked section heights).
static const _kRailPreviewHeight = 220.0;
static const _kCompactRailPreviewHeight = 140.0;
double get _contentHeight => settings.tabBarPosition.isVertical
? (compact ? _kCompactRailPreviewHeight : _kRailPreviewHeight)
: _baseHeight + _toolbarHeight;
@override
double get maxExtent =>
_baseHeight + _toolbarHeight + _headerHeight + padding.vertical;
double get minExtent => _contentHeight + _headerHeight + padding.vertical;
@override
double get maxExtent => _contentHeight + _headerHeight + padding.vertical;
@override
Widget build(
@@ -200,12 +207,17 @@ class TabBarPreviewCard extends HookWidget {
},
);
Widget buildQuickTabSwitcherRow(ScrollController scrollController) {
Widget buildQuickTabSwitcherRow(
ScrollController scrollController, {
Axis axis = Axis.horizontal,
}) {
return QuickTabSwitcherView(
availableItems: previewQuickItems,
activeItem: previewQuickItems.firstWhere((item) => item.isActive),
scrollController: scrollController,
showTitles: settings.quickTabSwitcherShowTitles,
axis: axis,
showTitles:
axis != Axis.vertical && settings.quickTabSwitcherShowTitles,
showIsolatedTabUi: settings.showIsolatedTabUi,
hierarchyGlyphs: settings.quickTabSwitcherHierarchyGlyphs,
titleMaxWidth: settings.quickTabSwitcherTitleWidth,
@@ -217,24 +229,25 @@ class TabBarPreviewCard extends HookWidget {
);
}
Widget buildQuickTabSwitcher() {
Widget buildQuickTabSwitcher({Axis axis = Axis.horizontal}) {
// The accordion preview reuses the single-row layout; container header
// chips need live container data that the static preview doesn't have.
if (settings.effectiveTabBarStackingMode() ==
TabBarStackingMode.twoLevel) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
buildQuickTabSwitcherRow(quickTabsController),
buildQuickTabSwitcherRow(quickTabsSecondRowController),
],
);
final rows = [
buildQuickTabSwitcherRow(quickTabsController, axis: axis),
buildQuickTabSwitcherRow(quickTabsSecondRowController, axis: axis),
];
return axis == Axis.vertical
? Column(children: [for (final row in rows) Expanded(child: row)])
: Column(mainAxisSize: MainAxisSize.min, children: rows);
}
return buildQuickTabSwitcherRow(quickTabsController);
return buildQuickTabSwitcherRow(quickTabsController, axis: axis);
}
Widget buildContextualToolbar() {
Widget buildContextualToolbar({Axis axis = Axis.horizontal}) {
return ContextualToolbarView(
axis: axis,
buttons: [
NavigateBackButtonView(
canGoBack: true,
@@ -307,43 +320,91 @@ class TabBarPreviewCard extends HookWidget {
contextualToolbar: buildContextualToolbar(),
);
final previewContent = Container(
clipBehavior: Clip.antiAlias,
final isRailPreview = settings.tabBarPosition.isVertical;
final railToolbar = BrowserTabBarView(
axis: Axis.vertical,
railOnLeft: settings.tabBarPosition == TabBarPosition.left,
showMainToolbar: true,
showContextualToolbar: settings.tabBarShowContextualBar,
showQuickTabSwitcherBar: showQuickTabSwitcherBar,
displayAppBar: true,
displayQuickTabSwitcher: true,
backgroundColor:
previewContainerPalette?.surfaceColor ?? colorScheme.surfaceContainer,
title: _RailPreviewTitle(
tabState: previewTabState,
quarterTurns: settings.tabBarPosition == TabBarPosition.left ? 3 : 1,
),
actions: mainToolbarActions,
quickTabSwitcher: buildQuickTabSwitcher(axis: Axis.vertical),
contextualToolbar: buildContextualToolbar(axis: Axis.vertical),
);
final pageContentBox = Container(
width: double.infinity,
height: double.infinity,
alignment: Alignment.center,
decoration: BoxDecoration(
color: compact
? colorScheme.surface.withValues(alpha: 0.7)
: colorScheme.surface,
border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(4),
? colorScheme.surfaceContainerLowest.withValues(alpha: 0.7)
: colorScheme.surfaceContainerLowest,
border: Border.symmetric(
horizontal: BorderSide(color: colorScheme.outlineVariant),
),
),
child: Column(
children: [
if (settings.tabBarPosition == TabBarPosition.top) topMainToolbar,
Container(
height: compact ? 40 : 72,
width: double.infinity,
alignment: Alignment.center,
decoration: BoxDecoration(
color: compact
? colorScheme.surfaceContainerLowest.withValues(alpha: 0.7)
: colorScheme.surfaceContainerLowest,
border: Border.symmetric(
horizontal: BorderSide(color: colorScheme.outlineVariant),
),
),
child: Text(
'Page Content',
style: Theme.of(context).textTheme.labelMedium,
),
),
if (settings.tabBarPosition == TabBarPosition.top)
topBottomToolbar
else
bottomCombinedToolbar,
],
child: Text(
'Page Content',
style: Theme.of(context).textTheme.labelMedium,
),
);
final Widget previewContent;
if (isRailPreview) {
final rail = SizedBox(
width: BrowserTabBar.sideRailWidth,
child: railToolbar,
);
final railOnLeft = settings.tabBarPosition == TabBarPosition.left;
previewContent = Container(
clipBehavior: Clip.antiAlias,
height: compact ? 140 : 220,
decoration: BoxDecoration(
color: compact
? colorScheme.surface.withValues(alpha: 0.7)
: colorScheme.surface,
border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: railOnLeft
? [rail, Expanded(child: pageContentBox)]
: [Expanded(child: pageContentBox), rail],
),
);
} else {
previewContent = Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: compact
? colorScheme.surface.withValues(alpha: 0.7)
: colorScheme.surface,
border: Border.all(color: colorScheme.outlineVariant),
borderRadius: BorderRadius.circular(4),
),
child: Column(
children: [
if (settings.tabBarPosition == TabBarPosition.top) topMainToolbar,
SizedBox(height: compact ? 40 : 72, child: pageContentBox),
if (settings.tabBarPosition == TabBarPosition.top)
topBottomToolbar
else
bottomCombinedToolbar,
],
),
);
}
if (compact) {
return previewContent;
}
@@ -409,4 +470,25 @@ class _CompactPreviewTitle extends StatelessWidget {
}
}
class _RailPreviewTitle extends StatelessWidget {
const _RailPreviewTitle({required this.tabState, required this.quarterTurns});
final TabState tabState;
final int quarterTurns;
@override
Widget build(BuildContext context) {
return RailAppBarTitleView(
tabState: tabState,
quarterTurns: quarterTurns,
isTabTunneled: false,
siteSettingsBadgeState: SiteSettingsBadgeState.hidden,
onSiteSettingsTap: _noop,
onTitleTap: _noop,
tabIcon: const Icon(MdiIcons.web, size: 24),
longPressUrlCopy: false,
);
}
}
void _noop() {}