Convert button .icon
and .tonalIcon
constructors to take nullable icons. (#142644)
## Description This changes the factory constructors for `TextButton.icon`, `ElevatedButton.icon`, `FilledButton.icon`, and `FilledButton.tonalIcon` to take nullable icons. If the icon is null, then the "regular" version of the button is created. ## Tests - Added tests for all four constructors.
This commit is contained in:
parent
b34ee07372
commit
2652b9a305
@ -81,6 +81,8 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
///
|
///
|
||||||
/// The icon and label are arranged in a row and padded by 12 logical pixels
|
/// The icon and label are arranged in a row and padded by 12 logical pixels
|
||||||
/// at the start, and 16 at the end, with an 8 pixel gap in between.
|
/// at the start, and 16 at the end, with an 8 pixel gap in between.
|
||||||
|
///
|
||||||
|
/// If [icon] is null, will create an [ElevatedButton] instead.
|
||||||
factory ElevatedButton.icon({
|
factory ElevatedButton.icon({
|
||||||
Key? key,
|
Key? key,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
@ -92,9 +94,39 @@ class ElevatedButton extends ButtonStyleButton {
|
|||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
Clip? clipBehavior,
|
||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
required Widget icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) = _ElevatedButtonWithIcon;
|
}) {
|
||||||
|
if (icon == null) {
|
||||||
|
return ElevatedButton(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHover: onHover,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _ElevatedButtonWithIcon(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHover: onHover,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// A static convenience method that constructs an elevated button
|
/// A static convenience method that constructs an elevated button
|
||||||
/// [ButtonStyle] given simple values.
|
/// [ButtonStyle] given simple values.
|
||||||
|
@ -81,6 +81,8 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
///
|
///
|
||||||
/// The icon and label are arranged in a row with padding at the start and end
|
/// The icon and label are arranged in a row with padding at the start and end
|
||||||
/// and a gap between them.
|
/// and a gap between them.
|
||||||
|
///
|
||||||
|
/// If [icon] is null, will create a [FilledButton] instead.
|
||||||
factory FilledButton.icon({
|
factory FilledButton.icon({
|
||||||
Key? key,
|
Key? key,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
@ -92,9 +94,39 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
Clip? clipBehavior,
|
||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
required Widget icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) = _FilledButtonWithIcon;
|
}) {
|
||||||
|
if (icon == null) {
|
||||||
|
return FilledButton(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHover: onHover,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _FilledButtonWithIcon(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHover: onHover,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a tonal variant of FilledButton.
|
/// Create a tonal variant of FilledButton.
|
||||||
///
|
///
|
||||||
@ -118,8 +150,10 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
|
|
||||||
/// Create a filled tonal button from [icon] and [label].
|
/// Create a filled tonal button from [icon] and [label].
|
||||||
///
|
///
|
||||||
/// The icon and label are arranged in a row with padding at the start and end
|
/// The [icon] and [label] are arranged in a row with padding at the start and
|
||||||
/// and a gap between them.
|
/// end and a gap between them.
|
||||||
|
///
|
||||||
|
/// If [icon] is null, will create a [FilledButton.tonal] instead.
|
||||||
factory FilledButton.tonalIcon({
|
factory FilledButton.tonalIcon({
|
||||||
Key? key,
|
Key? key,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
@ -131,9 +165,24 @@ class FilledButton extends ButtonStyleButton {
|
|||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
Clip? clipBehavior,
|
||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
required Widget icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) {
|
}) {
|
||||||
|
if (icon == null) {
|
||||||
|
return FilledButton.tonal(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHover: onHover,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
return _FilledButtonWithIcon.tonal(
|
return _FilledButtonWithIcon.tonal(
|
||||||
key: key,
|
key: key,
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
|
@ -85,6 +85,8 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
///
|
///
|
||||||
/// The icon and label are arranged in a row and padded by 12 logical pixels
|
/// The icon and label are arranged in a row and padded by 12 logical pixels
|
||||||
/// at the start, and 16 at the end, with an 8 pixel gap in between.
|
/// at the start, and 16 at the end, with an 8 pixel gap in between.
|
||||||
|
///
|
||||||
|
/// If [icon] is null, will create an [OutlinedButton] instead.
|
||||||
factory OutlinedButton.icon({
|
factory OutlinedButton.icon({
|
||||||
Key? key,
|
Key? key,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
@ -94,9 +96,35 @@ class OutlinedButton extends ButtonStyleButton {
|
|||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
Clip? clipBehavior,
|
||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
required Widget icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) = _OutlinedButtonWithIcon;
|
}) {
|
||||||
|
if (icon == null) {
|
||||||
|
return OutlinedButton(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _OutlinedButtonWithIcon(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// A static convenience method that constructs an outlined button
|
/// A static convenience method that constructs an outlined button
|
||||||
/// [ButtonStyle] given simple values.
|
/// [ButtonStyle] given simple values.
|
||||||
|
@ -105,9 +105,38 @@ class TextButton extends ButtonStyleButton {
|
|||||||
bool? autofocus,
|
bool? autofocus,
|
||||||
Clip? clipBehavior,
|
Clip? clipBehavior,
|
||||||
MaterialStatesController? statesController,
|
MaterialStatesController? statesController,
|
||||||
required Widget icon,
|
Widget? icon,
|
||||||
required Widget label,
|
required Widget label,
|
||||||
}) = _TextButtonWithIcon;
|
}) {
|
||||||
|
if (icon == null) {
|
||||||
|
return TextButton(
|
||||||
|
key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHover: onHover,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
child: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _TextButtonWithIcon( key: key,
|
||||||
|
onPressed: onPressed,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
onHover: onHover,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
style: style,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: autofocus ?? false,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.none,
|
||||||
|
statesController: statesController,
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// A static convenience method that constructs a text button
|
/// A static convenience method that constructs a text button
|
||||||
/// [ButtonStyle] given simple values.
|
/// [ButtonStyle] given simple values.
|
||||||
|
@ -159,6 +159,45 @@ void main() {
|
|||||||
expect(material.type, MaterialType.button);
|
expect(material.type, MaterialType.button);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ElevatedButton.icon produces the correct widgets if icon is null', (WidgetTester tester) async {
|
||||||
|
const ColorScheme colorScheme = ColorScheme.light();
|
||||||
|
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
|
||||||
|
final Key iconButtonKey = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsOneWidget);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
// No icon specified.
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsNothing);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Default ElevatedButton meets a11y contrast guidelines', (WidgetTester tester) async {
|
testWidgets('Default ElevatedButton meets a11y contrast guidelines', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
|
@ -127,6 +127,84 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('FilledButton.icon produces the correct widgets if icon is null', (WidgetTester tester) async {
|
||||||
|
const ColorScheme colorScheme = ColorScheme.light();
|
||||||
|
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
|
||||||
|
final Key iconButtonKey = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsOneWidget);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: FilledButton.icon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
// No icon specified.
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsNothing);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('FilledButton.tonalIcon produces the correct widgets if icon is null', (WidgetTester tester) async {
|
||||||
|
const ColorScheme colorScheme = ColorScheme.light();
|
||||||
|
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
|
||||||
|
final Key iconButtonKey = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: FilledButton.tonalIcon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsOneWidget);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: FilledButton.tonalIcon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
// No icon specified.
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsNothing);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('FilledButton.tonal, FilledButton.tonalIcon defaults', (WidgetTester tester) async {
|
testWidgets('FilledButton.tonal, FilledButton.tonalIcon defaults', (WidgetTester tester) async {
|
||||||
const ColorScheme colorScheme = ColorScheme.light();
|
const ColorScheme colorScheme = ColorScheme.light();
|
||||||
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
|
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
|
||||||
|
@ -174,6 +174,45 @@ void main() {
|
|||||||
expect(material.type, MaterialType.button);
|
expect(material.type, MaterialType.button);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('OutlinedButton.icon produces the correct widgets if icon is null', (WidgetTester tester) async {
|
||||||
|
const ColorScheme colorScheme = ColorScheme.light();
|
||||||
|
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
|
||||||
|
final Key iconButtonKey = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsOneWidget);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
// No icon specified.
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsNothing);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('OutlinedButton default overlayColor resolves pressed state', (WidgetTester tester) async {
|
testWidgets('OutlinedButton default overlayColor resolves pressed state', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
final ThemeData theme = ThemeData(useMaterial3: true);
|
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||||
|
@ -154,6 +154,45 @@ void main() {
|
|||||||
expect(material.type, MaterialType.button);
|
expect(material.type, MaterialType.button);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('TextButton.icon produces the correct widgets when icon is null', (WidgetTester tester) async {
|
||||||
|
const ColorScheme colorScheme = ColorScheme.light();
|
||||||
|
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
|
||||||
|
final Key iconButtonKey = UniqueKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: TextButton.icon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsOneWidget);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Center(
|
||||||
|
child: TextButton.icon(
|
||||||
|
key: iconButtonKey,
|
||||||
|
onPressed: () { },
|
||||||
|
// No icon specified.
|
||||||
|
label: const Text('label'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.add), findsNothing);
|
||||||
|
expect(find.text('label'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Default TextButton meets a11y contrast guidelines', (WidgetTester tester) async {
|
testWidgets('Default TextButton meets a11y contrast guidelines', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user