Repository: macosui/macos_ui

Issue:
MacosTextField expand with ResizablePane crashes
**Description**

<!-- Write a clear and concise description of what the bug is. -->

A MacosTextField wrapped with Resizable Pane with expand, no maxlines crashes with the error '''FlutterError (The Scrollbar's ScrollController has no ScrollPosition attached.
A Scrollbar cannot be painted without a ScrollPosition.
The Scrollbar attempted to use the provided ScrollController. This ScrollController should be associated with the ScrollView that the Scrollbar is being applied to.When providing your own ScrollController, ensure both the Scrollbar and the Scrollable widget use the same one.)'''


Use textfield with generic code like this:
```dart
ResizablePane(
  minSize: 50,
  startSize: 60,
  resizableSide: ResizableSide.top,
  maxSize: MediaQuery.of(context).size.height / 2.5,
  builder: (_, __) {
    return MacosTextField(
      prefix: MacosIcon(CupertinoIcons.money_dollar),
      placeholder: 'Type some text here',
      expands: true,
      maxLines: null,
      showCursor: true,
      clearButtonMode: OverlayVisibilityMode.always,
    );
  },
),
```




Discussion: Hey @robaolana, I can actually reproduce the issue.

`ResizablePane` has an internal `Scrollable` so making any widget a child of  `ResisablePane` is some way wrapping it with `Scrollable`.  You are supposed to give  the `scrollController` of the `ResizablePane.builder` to the main widget which has a scroll behaviour in your widget tree. Since the `MacosTextField` like material `TextField`  content can be scrolled when it overflows the view port it has a scroll behaviour.

Also it should not crash the app, you should just have the error stack trace in your console when scrolling the content of the `MacosTextField` but visually everything should seem to be working fine.

### Poposal
Try this...
```dart
ResizablePane(
minSize: 50,
startSize: 60,
resizableSide: ResizableSide.top,
maxSize: MediaQuery.of(context).size.height / 2.5,
builder: (_, scrollController) {
   return MacosTextField(
       //Passing the  builder scrollController  to the [MacosTextField]
       scrollController: scrollController,
       prefix: MacosIcon(CupertinoIcons.money_dollar),
       placeholder: 'Type some text here',
       expands: true,
       maxLines: null,
       showCursor: true,
       clearButtonMode: OverlayVisibilityMode.always,
),);
},
)
```
@GroovinChip, Shouldn't be nice to add a property and maybe a Constructor that would allow the disabling of the scroll behavior of the `Resizablepane` ?

Sometimes I use scrollable widgets which don't expose their controller, so the only way to avoid this error is to wrap them inside another `Scrollable` widget which expose his controller.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10ee2492..88c90a37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## [2.0.0-beta.9]
+* `ResizablePane` can now disallow the usage of its internal scrollbar via the  `ReziablePane.noScrollBar` constructor.
+
 ## [2.0.0-beta.8]
 ✨ New ✨
 * `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`.
diff --git a/example/lib/pages/resizable_pane_page.dart b/example/lib/pages/resizable_pane_page.dart
index b850a0c3..78ed3320 100644
--- a/example/lib/pages/resizable_pane_page.dart
+++ b/example/lib/pages/resizable_pane_page.dart
@@ -73,16 +73,12 @@ class _ResizablePanePageState extends State<ResizablePanePage> {
             );
           },
         ),
-        ResizablePane(
+        const ResizablePane.noScrollBar(
           minSize: 180,
           startSize: 200,
-          // windowBreakpoint: 800,
-          resizableSide: ResizableSide.left,
-          builder: (_, __) {
-            return const Center(
-              child: Text('Right Resizable Pane'),
-            );
-          },
+          windowBreakpoint: 700,
+          resizableSide: ResizableSide.right,
+          child: Center(child: Text('Right non-scrollable Resizable Pane')),
         ),
       ],
     );
diff --git a/example/lib/widgets/widget_text_title1.dart b/example/lib/widgets/widget_text_title1.dart
index d7dafc90..77ab7314 100644
--- a/example/lib/widgets/widget_text_title1.dart
+++ b/example/lib/widgets/widget_text_title1.dart
@@ -27,4 +27,4 @@ class WidgetTextTitle1 extends StatelessWidget {
       ),
     );
   }
-}
\ No newline at end of file
+}
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 6ef37937..8872a184 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -145,7 +145,7 @@ packages:
       path: ".."
       relative: true
     source: path
-    version: "2.0.0-beta.8"
+    version: "2.0.0-beta.9"
   macos_window_utils:
     dependency: transitive
     description:
diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart
index b3f6b338..97836bdd 100644
--- a/lib/src/layout/resizable_pane.dart
+++ b/lib/src/layout/resizable_pane.dart
@@ -30,10 +30,15 @@ enum ResizableSide {
 /// The [startSize] is the initial width or height depending on the orientation of the pane.
 /// {@endtemplate}
 class ResizablePane extends StatefulWidget {
-  /// {@macro resizablePane}
+  /// Creates a [ResizablePane] with an internal [MacosScrollbar].
+  ///
+  /// Consider using [ResizablePane.noScrollBar] constructor when the internal
+  /// [MacosScrollbar] is not needed or when working with widgets which do not
+  /// expose their scroll controllers.
+  /// {@macro resizablePane}.
   const ResizablePane({
     super.key,
-    required this.builder,
+    required ScrollableWidgetBuilder this.builder,
     this.decoration,
     this.maxSize = 500.0,
     required this.minSize,
@@ -41,7 +46,38 @@ class ResizablePane extends StatefulWidget {
     required this.resizableSide,
     this.windowBreakpoint,
     required this.startSize,
-  })  : assert(
+  })  : child = null,
+        useScrollBar = true,
+        assert(
+          maxSize >= minSize,
+          'minSize should not be more than maxSize.',
+        ),
+        assert(
+          (startSize >= minSize) && (startSize <= maxSize),
+          'startSize must not be less than minSize or more than maxWidth',
+        );
+
+  /// Creates a [ResizablePane] without an internal [MacosScrollbar].
+  ///
+  /// Useful when working with widgets which do not expose their scroll
+  /// controllers or when not using the platform scroll bar is preferred.
+  ///
+  /// Consider using the default constructor if showing a [MacosScrollbar]
+  /// when scrolling the content of this widget is the expected behavior.
+  /// {@macro resizablePane}.
+  const ResizablePane.noScrollBar({
+    super.key,
+    required Widget this.child,
+    this.decoration,
+    this.maxSize = 500.0,
+    required this.minSize,
+    this.isResizable = true,
+    required this.resizableSide,
+    this.windowBreakpoint,
+    required this.startSize,
+  })  : builder = null,
+        useScrollBar = false,
+        assert(
           maxSize >= minSize,
           'minSize should not be more than maxSize.',
         ),
@@ -55,7 +91,15 @@ class ResizablePane extends StatefulWidget {
   ///
   /// Pass the [scrollController] obtained from this method, to a scrollable
   /// widget used in this method to work with the internal [MacosScrollbar].
-  final ScrollableWidgetBuilder builder;
+  final ScrollableWidgetBuilder? builder;
+
+  /// The child to display in this widget.
+  ///
+  /// This is only referenced when the constructor used is [ResizablePane.noScrollbar].
+  final Widget? child;
+
+  /// Specify if this [ResizablePane] should have an internal [MacosScrollbar].
+  final bool useScrollBar;
 
   /// The [BoxDecoration] to paint behind the child in the [builder].
   final BoxDecoration? decoration;
@@ -277,10 +321,12 @@ class _ResizablePaneState extends State<ResizablePane> {
           SafeArea(
             left: false,
             right: false,
-            child: MacosScrollbar(
-              controller: _scrollController,
-              child: widget.builder(context, _scrollController),
-            ),
+            child: widget.useScrollBar
+                ? MacosScrollbar(
+                    controller: _scrollController,
+                    child: widget.builder!(context, _scrollController),
+                  )
+                : widget.child!,
           ),
           if (widget.isResizable && !_resizeOnRight && !_resizeOnTop)
             Positioned(
diff --git a/pubspec.yaml b/pubspec.yaml
index 4c2877e4..c14c5d6b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: macos_ui
 description: Flutter widgets and themes implementing the current macOS design language.
-version: 2.0.0-beta.8
+version: 2.0.0-beta.9
 homepage: "https://macosui.dev"
 repository: "https://github.com/GroovinChip/macos_ui"
 


Test patch:
diff --git a/test/layout/resizeable_pane_test.dart b/test/layout/resizeable_pane_test.dart
index 56164763..f3ac9f83 100644
--- a/test/layout/resizeable_pane_test.dart
+++ b/test/layout/resizeable_pane_test.dart
@@ -6,6 +6,10 @@ void main() {
   const matrix = ResizableSide.values;
 
   group('ResizablePane', () {
+    const double maxSize = 300;
+    const double minSize = 100;
+    const double startSize = 200;
+
     for (var side in matrix) {
       bool verticallyResizable = side == ResizableSide.top;
 
@@ -14,10 +18,6 @@ void main() {
             ? 'top'
             : (side == ResizableSide.left ? 'left' : 'right'),
         () {
-          const double maxSize = 300;
-          const double minSize = 100;
-          const double startSize = 200;
-
           final resizablePane = ResizablePane(
             builder: (context, scrollController) => const Text('Hello there'),
             minSize: minSize,
@@ -79,6 +79,238 @@ void main() {
           final double safeDelta = 50.0 * directionModifier;
           final double overflowDelta = 500.0 * directionModifier;
 
+          testWidgets(
+            'Default ResizablePane Constructor comes with an internal MacosScrollBar',
+            (WidgetTester tester) async {
+              await tester.pumpWidget(view);
+              expect(find.byType(MacosScrollbar), findsOneWidget);
+            },
+          );
+
+          testWidgets('initial size equals startSize', (tester) async {
+            await tester.pumpWidget(view);
+
+            var resizablePaneRenderObject =
+                tester.renderObject<RenderBox>(resizablePaneFinder);
+            var initialSize = verticallyResizable
+                ? resizablePaneRenderObject.size.height
+                : resizablePaneRenderObject.size.width;
+
+            expect(initialSize, startSize);
+          });
+
+          testWidgets('dragging wider works $side', (tester) async {
+            await tester.pumpWidget(view);
+
+            await tester.drag(
+              dragFinder,
+              verticallyResizable ? Offset(0, safeDelta) : Offset(safeDelta, 0),
+            );
+            await tester.pump();
+
+            var resizablePaneRenderObject =
+                tester.renderObject<RenderBox>(resizablePaneFinder);
+            expect(
+              verticallyResizable
+                  ? resizablePaneRenderObject.size.height
+                  : resizablePaneRenderObject.size.width,
+              startSize + safeDelta * directionModifier,
+            );
+          });
+
+          testWidgets('dragging wider respects maxSize', (tester) async {
+            await tester.pumpWidget(view);
+
+            await tester.drag(
+              dragFinder,
+              verticallyResizable
+                  ? Offset(0, overflowDelta)
+                  : Offset(overflowDelta, 0),
+            );
+            await tester.pump();
+
+            var resizablePaneRenderObject =
+                tester.renderObject<RenderBox>(resizablePaneFinder);
+            var currentSize = verticallyResizable
+                ? resizablePaneRenderObject.size.height
+                : resizablePaneRenderObject.size.width;
+            expect(currentSize, maxSize);
+          });
+
+          testWidgets(
+            'drag events past maxSize have no effect $side',
+            (tester) async {
+              await tester.pumpWidget(view);
+
+              final dragStartLocation = tester.getCenter(dragFinder);
+              final drag = await tester.startGesture(dragStartLocation);
+              await drag.moveBy(
+                verticallyResizable
+                    ? Offset(0, overflowDelta)
+                    : Offset(overflowDelta, 0),
+              );
+              await drag.moveBy(
+                verticallyResizable
+                    ? Offset(0, -10.0 * directionModifier)
+                    : Offset(-10.0 * directionModifier, 0),
+              );
+              await drag.up();
+              await tester.pump();
+
+              var resizablePaneRenderObject =
+                  tester.renderObject<RenderBox>(resizablePaneFinder);
+              var currentSize = verticallyResizable
+                  ? resizablePaneRenderObject.size.height
+                  : resizablePaneRenderObject.size.width;
+              expect(currentSize, maxSize);
+            },
+          );
+
+          testWidgets('dragging narrower works', (tester) async {
+            await tester.pumpWidget(view);
+
+            await tester.drag(
+              dragFinder,
+              verticallyResizable
+                  ? Offset(0, -safeDelta)
+                  : Offset(-safeDelta, 0),
+            );
+            await tester.pump();
+
+            var resizablePaneRenderObject =
+                tester.renderObject<RenderBox>(resizablePaneFinder);
+            var currentSize = verticallyResizable
+                ? resizablePaneRenderObject.size.height
+                : resizablePaneRenderObject.size.width;
+            expect(
+              currentSize,
+              startSize - safeDelta * directionModifier,
+            );
+          });
+
+          testWidgets('dragging narrower respects minSize', (tester) async {
+            await tester.pumpWidget(view);
+
+            await tester.drag(
+              dragFinder,
+              verticallyResizable
+                  ? Offset(0, -overflowDelta)
+                  : Offset(-overflowDelta, 0),
+            );
+            await tester.pump();
+
+            var resizablePaneRenderObject =
+                tester.renderObject<RenderBox>(resizablePaneFinder);
+            var currentSize = verticallyResizable
+                ? resizablePaneRenderObject.size.height
+                : resizablePaneRenderObject.size.width;
+            expect(currentSize, minSize);
+          });
+
+          testWidgets(
+            'drag events past minSize have no effect',
+            (tester) async {
+              await tester.pumpWidget(view);
+
+              final dragStartLocation = tester.getCenter(dragFinder);
+              final drag = await tester.startGesture(dragStartLocation);
+              await drag.moveBy(
+                verticallyResizable
+                    ? Offset(0, -overflowDelta)
+                    : Offset(-overflowDelta, 0),
+              );
+              await drag.moveBy(
+                verticallyResizable
+                    ? Offset(0, 10.0 * directionModifier)
+                    : Offset(10.0 * directionModifier, 0),
+              );
+              await drag.up();
+              await tester.pump();
+
+              var resizablePaneRenderObject =
+                  tester.renderObject<RenderBox>(resizablePaneFinder);
+              var currentSize = verticallyResizable
+                  ? resizablePaneRenderObject.size.height
+                  : resizablePaneRenderObject.size.width;
+              expect(currentSize, minSize);
+            },
+          );
+        },
+      );
+      group(
+        side == ResizableSide.top
+            ? 'top'
+            : (side == ResizableSide.left ? 'left' : 'right'),
+        () {
+          final resizablePane = ResizablePane.noScrollBar(
+            minSize: minSize,
+            startSize: startSize,
+            maxSize: maxSize,
+            resizableSide: side,
+            child: const Text('Hello there'),
+          );
+
+          final view = side == ResizableSide.top
+              ? MacosApp(
+                  home: MacosWindow(
+                    disableWallpaperTinting: true,
+                    child: MacosScaffold(
+                      children: [
+                        ContentArea(
+                          builder: (context, scrollController) {
+                            return Column(
+                              children: [
+                                const Flexible(
+                                  fit: FlexFit.loose,
+                                  child: Center(
+                                    child: Text('Hello there'),
+                                  ),
+                                ),
+                                resizablePane,
+                              ],
+                            );
+                          },
+                        ),
+                      ],
+                    ),
+                  ),
+                )
+              : MacosApp(
+                  home: MacosWindow(
+                    disableWallpaperTinting: true,
+                    child: MacosScaffold(
+                      children: [
+                        resizablePane,
+                        ContentArea(
+                          builder: (context, scrollController) {
+                            return const Text('Hello there');
+                          },
+                        ),
+                      ],
+                    ),
+                  ),
+                );
+
+          final resizablePaneFinder = find.byWidget(resizablePane);
+          final dragFinder = find.descendant(
+            of: resizablePaneFinder,
+            matching: find.byType(GestureDetector),
+          );
+
+          // No need to check if the resizable side is top because directionModifier
+          // would take -1 if it is the case
+          final directionModifier = side == ResizableSide.right ? 1 : -1;
+          final double safeDelta = 50.0 * directionModifier;
+          final double overflowDelta = 500.0 * directionModifier;
+
+          testWidgets(
+            'ResizablePane.noScrollBar Constructor does not come with an internal MacosScrollBar',
+            (WidgetTester tester) async {
+              await tester.pumpWidget(view);
+              expect(find.byType(MacosScrollbar), findsNothing);
+            },
+          );
+
           testWidgets('initial size equals startSize', (tester) async {
             await tester.pumpWidget(view);
 
diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart
index e5f4f495..f0996546 100644
--- a/test/selectors/date_picker_test.dart
+++ b/test/selectors/date_picker_test.dart
@@ -84,30 +84,32 @@ void main() {
 
     testWidgets(
       'Textual MacosDatePicker renders the date with respect to "dateFormat" property',
-          (tester) async {
+      (tester) async {
         renderWidget(String dateFormat) => MacosApp(
-          home: MacosWindow(
-            disableWallpaperTinting: true,
-            child: MacosScaffold(
-              children: [
-                ContentArea(
-                  builder: (context, _) {
-                    return Center(
-                      child: MacosDatePicker(
-                        initialDate: DateTime.parse('2023-04-01'),
-                        onDateChanged: (date) {},
-                        dateFormat: dateFormat,
-                        style: DatePickerStyle.textual,
-                      ),
-                    );
-                  },
+              home: MacosWindow(
+                disableWallpaperTinting: true,
+                child: MacosScaffold(
+                  children: [
+                    ContentArea(
+                      builder: (context, _) {
+                        return Center(
+                          child: MacosDatePicker(
+                            initialDate: DateTime.parse('2023-04-01'),
+                            onDateChanged: (date) {},
+                            dateFormat: dateFormat,
+                            style: DatePickerStyle.textual,
+                          ),
+                        );
+                      },
+                    ),
+                  ],
                 ),
-              ],
-            ),
-          ),
-        );
+              ),
+            );
 
-        getNthTextFromWidget(int index) => (find.byType(Text).at(index).evaluate().first.widget as Text).data as String;
+        getNthTextFromWidget(int index) =>
+            (find.byType(Text).at(index).evaluate().first.widget as Text).data
+                as String;
 
         await tester.pumpWidget(renderWidget('dd.mm.yyyy'));
         String firstDateElement = getNthTextFromWidget(0);
@@ -363,7 +365,7 @@ void main() {
 
     testWidgets(
       'Graphical MacosDatePicker renders abbreviations based on "weekdayAbbreviations" and "monthAbbreviations" properties',
-          (tester) async {
+      (tester) async {
         await tester.pumpWidget(
           MacosApp(
             home: MacosWindow(
@@ -424,7 +426,7 @@ void main() {
 
   testWidgets(
     'Graphical MacosDatePicker with "startWeekOnMonday" set to true shows Monday as the first day of the week',
-        (tester) async {
+    (tester) async {
       await tester.pumpWidget(
         MacosApp(
           home: MacosWindow(
@@ -453,7 +455,8 @@ void main() {
         matching: find.byType(Text),
       );
       final firstWeekday = dayHeaders.first;
-      final firstWeekdayText = (firstWeekday.evaluate().first.widget as Text).data;
+      final firstWeekdayText =
+          (firstWeekday.evaluate().first.widget as Text).data;
       await tester.pumpAndSettle();
 
       expect(firstWeekdayText, 'Mo');
@@ -474,7 +477,7 @@ void main() {
   // TODO: remove this once the issue is fixed and test starts failing
   testWidgets(
     'Graphical MacosDatePicker still needs "startWeekOnMonday" to show Monday as the first day of the week, even when the locale is set to something other than "en_US"',
-        (tester) async {
+    (tester) async {
       await tester.pumpWidget(
         MacosApp(
           supportedLocales: const [
@@ -506,7 +509,8 @@ void main() {
         matching: find.byType(Text),
       );
       final firstWeekday = dayHeaders.first;
-      final firstWeekdayText = (firstWeekday.evaluate().first.widget as Text).data;
+      final firstWeekdayText =
+          (firstWeekday.evaluate().first.widget as Text).data;
       await tester.pumpAndSettle();
 
       // The result will be 'Tu' if the fix is no longer needed and can be removed