As mentioned in "Flutter Is Easier With A MUI 3 Inspired Design", one ends up having to write a lot of boilerplate when you want to do build something that didn't adhere to a MUI3 design. Most of the button-like elements in the app I was building didn't have
Inkwells
, some didn't look like a button (think a clickable Card), so I ended up having to code up a solution:clickable.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Clickable extends StatelessWidget {
const Clickable({
super.key,
required this.onPressed,
required this.child,
this.cursor = MaterialStateMouseCursor.clickable,
this.onEnter,
this.onExit,
});
final Widget child;
final Function() onPressed;
final MaterialStateMouseCursor cursor;
final PointerEnterEventListener? onEnter;
final PointerExitEventListener? onExit;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
child: MouseRegion(
cursor: cursor,
onEnter: onEnter,
onExit: onExit,
child: child,
),
);
}
}
Nice, simple and re-usable.
clickable_test.dart
import 'dart:async';
import 'dart:ui';
import 'package:app/components/ui/helpers/clickable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('Clickable', () {
testWidgets('onPressed callback is called when tapped', (tester) async {
final completer = Completer<void>();
final widget = Clickable(
onPressed: completer.complete,
child: Container(),
);
// Pump the widget into the widget tree.
await tester.pumpWidget(widget);
// Find the Clickable widget in the widget tree.
final finder = find.byType(Clickable);
// Tap the Clickable widget.
await tester.tap(finder);
// Verify that the onPressed callback was called.
expect(completer.isCompleted, true);
});
testWidgets('onEnter callback is called when hovered', (tester) async {
final completer = Completer<void>();
final widget = Container(
margin: const EdgeInsets.all(100),
child: Clickable(
onEnter: completer.complete,
onPressed: () {},
child: const SizedBox(width: 10, height: 10),
),
);
// Pump the widget into the widget tree.
await tester.pumpWidget(widget);
final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Clickable)));
// Verify that the onEnter callback was called.
expect(completer.isCompleted, true);
});
testWidgets('onExit callback is called when hovered off', (tester) async {
final completer = Completer<void>();
// Create a test widget.
final widget = Container(
margin: const EdgeInsets.all(100),
child: Clickable(
onExit: completer.complete,
onPressed: () {},
child: const SizedBox(width: 10, height: 10),
),
);
await tester.pumpWidget(widget);
final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Clickable)));
await gesture.moveTo(Offset.zero);
// Verify that the onExit callback was called.
expect(completer.isCompleted, true);
});
});
}