diff --git a/packages/flet/lib/src/controls/tabs.dart b/packages/flet/lib/src/controls/tabs.dart index fb10ad8b36..5904f73b3b 100644 --- a/packages/flet/lib/src/controls/tabs.dart +++ b/packages/flet/lib/src/controls/tabs.dart @@ -157,35 +157,48 @@ class TabBarViewControl extends StatelessWidget { // Find the TabController from the nearest TabsControl ancestor final tabsState = context.findAncestorStateOfType<_TabsControlState>(); - if (tabsState != null) { - final tabController = tabsState._tabController; + if (tabsState == null) { + return const ErrorControl( + "TabBarView must be used within a Tabs control"); + } + + final tabController = tabsState._tabController; + + Widget buildConstrainedTabView() { + return ConstrainedControl( + control: control, + child: TabBarView( + controller: tabController, + clipBehavior: + control.getClipBehavior("clip_behavior", Clip.hardEdge)!, + viewportFraction: control.getDouble("viewport_fraction", 1.0)!, + children: control.buildWidgets("controls"), + )); + } - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - if (constraints.maxHeight == double.infinity && - control.getDouble("height") == null && - control.getExpand("expand", 0)! <= 0) { + // If expand property was set, we return the result directly. + // Because having Expanded as direct child of LayoutBuilder is not allowed. + if (control.getExpand("expand", 0)! > 0) { + return buildConstrainedTabView(); + } + + return LayoutBuilder( + builder: (context, constraints) { + final hasFixedHeight = control.getDouble("height") != null; + final hasUnboundedHeight = + constraints.maxHeight == double.infinity && !hasFixedHeight; + + if (hasUnboundedHeight) { return const ErrorControl( - "Error displaying TabBarView: height is unbounded.", - description: - "Set a fixed height, a non-zero expand, or/and place inside " - "a control with bounded height."); + "Error displaying TabBarView: height is unbounded.", + description: + "Set a fixed height, a non-zero expand, or place it inside a control with bounded height.", + ); } - return ConstrainedControl( - control: control, - child: TabBarView( - controller: tabController, - clipBehavior: - control.getClipBehavior("clip_behavior", Clip.hardEdge)!, - viewportFraction: control.getDouble("viewport_fraction", 1.0)!, - children: control.buildWidgets("controls"), - )); - }); - } else { - return const ErrorControl( - "TabBarView must be used within a Tabs control"); - } + return buildConstrainedTabView(); + }, + ); } } diff --git a/sdk/python/examples/controls/tabs/nested.py b/sdk/python/examples/controls/tabs/nested.py index d3c3bfc4b0..f898c85fd0 100644 --- a/sdk/python/examples/controls/tabs/nested.py +++ b/sdk/python/examples/controls/tabs/nested.py @@ -18,6 +18,7 @@ def main(page: ft.Page): ft.TabBarView( expand=True, controls=[ + ft.Text("Main Tab 1 content"), ft.Tabs( length=2, expand=True, @@ -41,7 +42,6 @@ def main(page: ft.Page): ], ), ), - ft.Text("Main Tab 1 content"), ], ), ], diff --git a/sdk/python/packages/flet/integration_tests/controls/golden/macos/tabs/basic.png b/sdk/python/packages/flet/integration_tests/controls/golden/macos/tabs/basic.png new file mode 100644 index 0000000000..9268dc8162 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/golden/macos/tabs/basic.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/golden/macos/tabs/nesting.png b/sdk/python/packages/flet/integration_tests/controls/golden/macos/tabs/nesting.png new file mode 100644 index 0000000000..87eaacdca0 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/controls/golden/macos/tabs/nesting.png differ diff --git a/sdk/python/packages/flet/integration_tests/controls/golden/macos/tabs/tabs_basic.png b/sdk/python/packages/flet/integration_tests/controls/golden/macos/tabs/tabs_basic.png deleted file mode 100644 index baad6ff9b0..0000000000 Binary files a/sdk/python/packages/flet/integration_tests/controls/golden/macos/tabs/tabs_basic.png and /dev/null differ diff --git a/sdk/python/packages/flet/integration_tests/controls/test_tabs.py b/sdk/python/packages/flet/integration_tests/controls/test_tabs.py index ba97e830ad..167eb5f890 100644 --- a/sdk/python/packages/flet/integration_tests/controls/test_tabs.py +++ b/sdk/python/packages/flet/integration_tests/controls/test_tabs.py @@ -5,7 +5,7 @@ @pytest.mark.asyncio(loop_scope="module") -async def test_tabs_basic(flet_app: ftt.FletTestApp, request): +async def test_basic(flet_app: ftt.FletTestApp, request): await flet_app.assert_control_screenshot( name=request.node.name, expand_screenshot=True, @@ -19,20 +19,71 @@ async def test_tabs_basic(flet_app: ftt.FletTestApp, request): ft.Tab(label="Tab 2", icon=ft.Icons.SETTINGS), ] ), - ft.Container( + ft.TabBarView( expand=True, - content=ft.TabBarView( - controls=[ - ft.Container( - content=ft.Text("This is Tab 1"), - alignment=ft.Alignment.CENTER, - ), - ft.Container( - content=ft.Text("This is Tab 3"), - alignment=ft.Alignment.CENTER, + controls=[ + ft.Container( + content=ft.Text("This is Tab 1"), + alignment=ft.Alignment.CENTER, + ), + ft.Container( + content=ft.Text("This is Tab 3"), + alignment=ft.Alignment.CENTER, + ), + ], + ), + ], + ), + ), + ) + + +@pytest.mark.asyncio(loop_scope="module") +async def test_nesting(flet_app: ftt.FletTestApp, request): + await flet_app.assert_control_screenshot( + name=request.node.name, + expand_screenshot=True, + control=ft.Tabs( + length=2, + expand=True, + selected_index=1, + content=ft.Column( + expand=True, + controls=[ + ft.TabBar( + tabs=[ + ft.Tab(label=ft.Text("Main Tab 1")), + ft.Tab(label=ft.Text("Main Tab 2")), + ], + ), + ft.TabBarView( + expand=True, + controls=[ + ft.Text("Main Tab 1 content"), + ft.Tabs( + length=2, + expand=True, + content=ft.Column( + expand=True, + controls=[ + ft.TabBar( + secondary=True, + tabs=[ + ft.Tab(label=ft.Text("SubTab 1")), + ft.Tab(label=ft.Text("SubTab 2")), + ], + ), + ft.TabBarView( + expand=True, + controls=[ + ft.Text("Nested Tab 1 content"), + ft.Text("Nested Tab 2 content"), + ], + ), + ], ), - ], - ), + ), + ], ), ], ),