Add option to hide trailing icon in DropdownMenu (#167782)

This PR adds support for hiding the trailing icon in `DropdownMenu`
widget. Currently, there's no built-in option to remove it.

The change is non-breaking, as the trailing icon remains visible by
default unless `showTrailingIcon` is explicitly set to `false`. The
`showTrailingIcon` parameter follows a similar pattern to its use in the
`ExpansionTile` widget.

Fixes #164908

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

---------

Co-authored-by: Qun Cheng <36861262+QuncCccccc@users.noreply.github.com>
This commit is contained in:
Mohamed Amara 2025-05-08 00:43:06 +01:00 committed by GitHub
parent 3a1c297206
commit 930d21504a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 72 additions and 17 deletions

View File

@ -164,6 +164,7 @@ class DropdownMenu<T> extends StatefulWidget {
this.menuHeight,
this.leadingIcon,
this.trailingIcon,
this.showTrailingIcon = true,
this.label,
this.hintText,
this.helperText,
@ -225,8 +226,18 @@ class DropdownMenu<T> extends StatefulWidget {
/// An optional icon at the end of the text field.
///
/// Defaults to an [Icon] with [Icons.arrow_drop_down].
///
/// If [showTrailingIcon] is false, the trailing icon will not be shown.
final Widget? trailingIcon;
/// Specifies if the [DropdownMenu] should show a [trailingIcon].
///
/// If [trailingIcon] is set, [DropdownMenu] will use that trailing icon,
/// otherwise a default trailing icon will be created.
///
/// Defaults to true.
final bool showTrailingIcon;
/// Optional widget that describes the input field.
///
/// When the input field is empty and unfocused, the label is displayed on
@ -1008,22 +1019,25 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
builder: (BuildContext context, MenuController controller, Widget? child) {
assert(_initialMenu != null);
final bool isCollapsed = widget.inputDecorationTheme?.isCollapsed ?? false;
final Widget trailingButton = Padding(
padding: isCollapsed ? EdgeInsets.zero : const EdgeInsets.all(4.0),
child: IconButton(
isSelected: controller.isOpen,
constraints: widget.inputDecorationTheme?.suffixIconConstraints,
padding: isCollapsed ? EdgeInsets.zero : null,
icon: widget.trailingIcon ?? const Icon(Icons.arrow_drop_down),
selectedIcon: widget.selectedTrailingIcon ?? const Icon(Icons.arrow_drop_up),
onPressed:
!widget.enabled
? null
: () {
handlePressed(controller);
},
),
);
final Widget trailingButton =
widget.showTrailingIcon
? Padding(
padding: isCollapsed ? EdgeInsets.zero : const EdgeInsets.all(4.0),
child: IconButton(
isSelected: controller.isOpen,
constraints: widget.inputDecorationTheme?.suffixIconConstraints,
padding: isCollapsed ? EdgeInsets.zero : null,
icon: widget.trailingIcon ?? const Icon(Icons.arrow_drop_down),
selectedIcon: widget.selectedTrailingIcon ?? const Icon(Icons.arrow_drop_up),
onPressed:
!widget.enabled
? null
: () {
handlePressed(controller);
},
),
)
: const SizedBox.shrink();
final Widget leadingButton = Padding(
padding: const EdgeInsets.all(8.0),
@ -1070,7 +1084,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
widget.leadingIcon != null
? SizedBox(key: _leadingKey, child: widget.leadingIcon)
: null,
suffixIcon: trailingButton,
suffixIcon: widget.showTrailingIcon ? trailingButton : null,
).applyDefaults(effectiveInputDecorationTheme),
restorationId: widget.restorationId,
);

View File

@ -4238,6 +4238,47 @@ void main() {
final TextField textField = tester.firstWidget(find.byType(TextField));
expect(textField.restorationId, restorationId);
});
testWidgets(
'DropdownMenu does not include the default trailing icon when showTrailingIcon is false',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DropdownMenu<TestMenu>(
showTrailingIcon: false,
dropdownMenuEntries: menuChildren,
),
),
),
);
await tester.pump();
final Finder iconButton = find.widgetWithIcon(IconButton, Icons.arrow_drop_down);
expect(iconButton, findsNothing);
},
);
testWidgets(
'DropdownMenu does not include the provided trailing icon when showTrailingIcon is false',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DropdownMenu<TestMenu>(
trailingIcon: const Icon(Icons.ac_unit),
showTrailingIcon: false,
dropdownMenuEntries: menuChildren,
),
),
),
);
await tester.pump();
final Finder iconButton = find.widgetWithIcon(IconButton, Icons.ac_unit);
expect(iconButton, findsNothing);
},
);
}
enum TestMenu {