diff --git a/apps/gallery/lib/sections/button_group_section.dart b/apps/gallery/lib/sections/button_group_section.dart index f820006..f5b7c35 100644 --- a/apps/gallery/lib/sections/button_group_section.dart +++ b/apps/gallery/lib/sections/button_group_section.dart @@ -39,6 +39,14 @@ class ButtonGroupSection extends StatelessWidget { title: 'ButtonGroupM3E — vertical, menu overflow', child: _demoVertical(context), ), + SectionCard( + title: 'ButtonGroupM3E — centered (expanded)', + child: _demoAlignedCenter(context), + ), + SectionCard( + title: 'ButtonGroupM3E — right aligned (expanded)', + child: _demoAlignedRight(context), + ), ], ); } @@ -48,14 +56,7 @@ class ButtonGroupSection extends StatelessWidget { width: 280, child: ButtonGroupM3E( actions: [ - for (final label in [ - 'One', - 'Two', - 'Three', - 'Four', - 'Five Button', - 'Six' - ]) + for (final label in ['One', 'Two', 'Three', 'Four', 'Five', 'Six']) ButtonGroupM3EAction(label: Text(label), onPressed: () {}), ], ), @@ -97,10 +98,10 @@ class ButtonGroupSection extends StatelessWidget { child: ButtonGroupM3E( type: ButtonGroupM3EType.connected, dividerColor: Theme.of(context).colorScheme.outlineVariant, - style: ButtonM3EStyle.tonal, actions: [ for (final label in ['Low', 'Med', 'High']) ButtonGroupM3EAction( + style: ButtonM3EStyle.tonal, label: Text(label), onPressed: () {}, ), @@ -136,15 +137,9 @@ class ButtonGroupSection extends StatelessWidget { dividerColor: Theme.of(context).colorScheme.outlineVariant, style: ButtonM3EStyle.tonal, actions: [ - for (final label in [ - 'One', - 'Two', - 'Three', - 'Four', - 'Five Button', - 'Six' - ]) + for (final label in ['One', 'Two', 'Three', 'Four', 'Five', 'Six']) ButtonGroupM3EAction( + style: ButtonM3EStyle.tonal, label: Text(label), onPressed: () {}, ), @@ -165,4 +160,31 @@ class ButtonGroupSection extends StatelessWidget { ), ); } + + Widget _demoAlignedCenter(BuildContext context) { + return ButtonGroupM3E( + expanded: true, + linearMainAxisAlignment: MainAxisAlignment.center, + actions: const [ + ButtonGroupM3EAction(label: Text('Center 1')), + ButtonGroupM3EAction(label: Text('Center 2')), + ButtonGroupM3EAction(label: Text('Center 3')), + ], + ); + } + + Widget _demoAlignedRight(BuildContext context) { + return ButtonGroupM3E( + expanded: true, + linearMainAxisAlignment: MainAxisAlignment.end, + overflow: ButtonGroupM3EOverflow.menu, + actions: const [ + ButtonGroupM3EAction(label: Text('Right 1')), + ButtonGroupM3EAction(label: Text('Right 2')), + ButtonGroupM3EAction(label: Text('Right 3')), + ButtonGroupM3EAction(label: Text('Right 4')), + ButtonGroupM3EAction(label: Text('Right 5')), + ], + ); + } } diff --git a/packages/button_group_m3e/CHANGELOG.md b/packages/button_group_m3e/CHANGELOG.md index 5275b1f..d6e5480 100644 --- a/packages/button_group_m3e/CHANGELOG.md +++ b/packages/button_group_m3e/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.1 +- New: `expanded` and `linearMainAxisAlignment` options for non-wrap layouts. When `expanded` is true, the group uses `MainAxisSize.max` and aligns items per `linearMainAxisAlignment` (or mapped from `alignment`). This enables internal center/right alignment without extra wrappers. +- Fix: Connected + menu overflow now consistently inserts a 2px gap before the overflow trigger (when `showDividers=false`) and uses a stricter width-fitting algorithm with a small epsilon to eliminate minor RenderFlex overflows. +- UX: Dropdown overflow popup is anchored bottom-right of the overflow button, has intrinsic width to fit its buttons, and aligns its internal buttons to the right. + ## 0.3.0 - Breaking: Removed legacy `children` parameter. Use `actions: List` instead. - Breaking: Renamed `groupSelection` API to `selection` for clarity. diff --git a/packages/button_group_m3e/lib/src/button_group_m3e_widget.dart b/packages/button_group_m3e/lib/src/button_group_m3e_widget.dart index 34fb83b..2a98cfe 100644 --- a/packages/button_group_m3e/lib/src/button_group_m3e_widget.dart +++ b/packages/button_group_m3e/lib/src/button_group_m3e_widget.dart @@ -98,6 +98,8 @@ class ButtonGroupM3E extends StatefulWidget { this.selection = false, this.selectedIndex, this.overflowMenuStyle = ButtonGroupM3EOverflowMenuStyle.dropdown, + this.expanded = false, + this.linearMainAxisAlignment, }); /// Declarative actions to build buttons. Overrides [children] when not empty. @@ -146,6 +148,13 @@ class ButtonGroupM3E extends StatefulWidget { /// How to display the overflow menu when [overflow] == menu. Defaults to dropdown. final ButtonGroupM3EOverflowMenuStyle overflowMenuStyle; + /// When true, Row/Column uses MainAxisSize.max enabling internal end alignment. + final bool expanded; + + /// Explicit mainAxis alignment for non-wrap (Row/Column) layouts. + /// If null falls back to mapping of [alignment] (WrapAlignment) for convenience. + final MainAxisAlignment? linearMainAxisAlignment; + bool get _connected => type == ButtonGroupM3EType.connected; @override @@ -270,14 +279,23 @@ class _ButtonGroupM3EState extends State { final list = _buildItemList( context, spacing, dividerColor, dividerThickness, count: _effectiveActions.length); + + final mainAlign = + widget.linearMainAxisAlignment ?? _mapWrapToMain(widget.alignment); + final mainSize = widget.expanded ? MainAxisSize.max : MainAxisSize.min; + return widget.direction == Axis.horizontal ? Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: mainSize, + mainAxisAlignment: + widget.expanded ? mainAlign : MainAxisAlignment.start, crossAxisAlignment: _mapCross(widget.crossAxisAlignment), children: list, ) : Column( - mainAxisSize: MainAxisSize.min, + mainAxisSize: mainSize, + mainAxisAlignment: + widget.expanded ? mainAlign : MainAxisAlignment.start, crossAxisAlignment: _mapCross(widget.crossAxisAlignment), children: list, ); @@ -289,6 +307,10 @@ class _ButtonGroupM3EState extends State { final list = _buildItemList( context, spacing, dividerColor, dividerThickness, count: _effectiveActions.length); + + final mainAlign = + widget.linearMainAxisAlignment ?? _mapWrapToMain(widget.alignment); + return LayoutBuilder( builder: (context, constraints) { final isBounded = widget.direction == Axis.horizontal @@ -297,12 +319,18 @@ class _ButtonGroupM3EState extends State { final core = widget.direction == Axis.horizontal ? Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: + widget.expanded ? MainAxisSize.max : MainAxisSize.min, + mainAxisAlignment: + widget.expanded ? mainAlign : MainAxisAlignment.start, crossAxisAlignment: _mapCross(widget.crossAxisAlignment), children: list, ) : Column( - mainAxisSize: MainAxisSize.min, + mainAxisSize: + widget.expanded ? MainAxisSize.max : MainAxisSize.min, + mainAxisAlignment: + widget.expanded ? mainAlign : MainAxisAlignment.start, crossAxisAlignment: _mapCross(widget.crossAxisAlignment), children: list, ); @@ -444,17 +472,25 @@ class _ButtonGroupM3EState extends State { } final coreChildren = visibleList; + final mainAlign = + widget.linearMainAxisAlignment ?? _mapWrapToMain(widget.alignment); final core = widget.direction == Axis.horizontal ? ClipRect( child: Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: + widget.expanded ? MainAxisSize.max : MainAxisSize.min, + mainAxisAlignment: + widget.expanded ? mainAlign : MainAxisAlignment.start, crossAxisAlignment: _mapCross(widget.crossAxisAlignment), children: coreChildren, ), ) : ClipRect( child: Column( - mainAxisSize: MainAxisSize.min, + mainAxisSize: + widget.expanded ? MainAxisSize.max : MainAxisSize.min, + mainAxisAlignment: + widget.expanded ? mainAlign : MainAxisAlignment.start, crossAxisAlignment: _mapCross(widget.crossAxisAlignment), children: coreChildren, ), @@ -765,6 +801,15 @@ class _ButtonGroupM3EState extends State { } } + MainAxisAlignment _mapWrapToMain(WrapAlignment w) => switch (w) { + WrapAlignment.start => MainAxisAlignment.start, + WrapAlignment.end => MainAxisAlignment.end, + WrapAlignment.center => MainAxisAlignment.center, + WrapAlignment.spaceBetween => MainAxisAlignment.spaceBetween, + WrapAlignment.spaceAround => MainAxisAlignment.spaceAround, + WrapAlignment.spaceEvenly => MainAxisAlignment.spaceEvenly, + }; + Widget _buildOverflowTrigger( BuildContext context, double spacing, @@ -830,7 +875,6 @@ class _ButtonGroupM3EState extends State { _removeOverflowEntry(); final overlay = Overlay.of(context, rootOverlay: true); - if (overlay == null) return; _overflowEntry = OverlayEntry( builder: (ctx) { diff --git a/packages/button_group_m3e/pubspec.yaml b/packages/button_group_m3e/pubspec.yaml index 364783e..84fb31b 100644 --- a/packages/button_group_m3e/pubspec.yaml +++ b/packages/button_group_m3e/pubspec.yaml @@ -1,6 +1,6 @@ name: button_group_m3e description: Wrapper-only Button Group for Material 3 Expressive (layout, shape, size propagation). -version: 0.3.0 +version: 0.3.1 repository: https://github.com/EmilyMoonstone/material_3_expressive/tree/main/packages/button_group_m3e issue_tracker: https://github.com/EmilyMonestone/material_3_expressive/issues diff --git a/packages/m3e_collection/CHANGELOG.md b/packages/m3e_collection/CHANGELOG.md index d780806..d207023 100644 --- a/packages/m3e_collection/CHANGELOG.md +++ b/packages/m3e_collection/CHANGELOG.md @@ -1,2 +1,5 @@ +## 0.3.7 +- Updated button_group_m3e to 0.3.1. + ## 0.1.0 - Changelog initialized. diff --git a/packages/m3e_collection/pubspec.yaml b/packages/m3e_collection/pubspec.yaml index c5e2947..7bd8082 100644 --- a/packages/m3e_collection/pubspec.yaml +++ b/packages/m3e_collection/pubspec.yaml @@ -1,6 +1,6 @@ name: m3e_collection description: Aggregated exports of all Material 3 Expressive components for Flutter. -version: 0.3.6 +version: 0.3.7 repository: https://github.com/EmilyMoonstone/material_3_expressive/tree/main/packages/m3e_collection issue_tracker: https://github.com/EmilyMonestone/material_3_expressive/issues @@ -9,7 +9,7 @@ environment: dependencies: app_bar_m3e: ^0.1.2 - button_group_m3e: ^0.3.0 + button_group_m3e: ^0.3.1 button_m3e: ^0.1.2 expressive_refresh: ^0.1.2 fab_m3e: ^0.1.1