Skip to content

Instantly share code, notes, and snippets.

@caseycrogers
Last active March 29, 2024 14:11
Show Gist options
  • Save caseycrogers/52ae39a18ea49dd64cae9741a6971f51 to your computer and use it in GitHub Desktop.
Save caseycrogers/52ae39a18ea49dd64cae9741a6971f51 to your computer and use it in GitHub Desktop.
class DropdownMenu extends StatefulWidget {
const DropdownMenu({
super.key,
required this.name,
required this.child,
required this.overlayBuilder,
});
final String name;
final Widget child;
/// Has access to the portal controller via a provider.
final WidgetBuilder overlayBuilder;
@override
State<DropdownMenu> createState() => _DropdownMenuState();
static OverlayPortalController of(BuildContext context) {
final _ControllerInjector? result =
context.dependOnInheritedWidgetOfExactType<_ControllerInjector>();
assert(result != null, 'No OverlayController found in context');
return result!.controller;
}
static OverlayPortalController? maybeOf(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<_ControllerInjector>()
?.controller;
}
}
class _DropdownMenuState extends State<DropdownMenu> {
final OverlayPortalController controller = OverlayPortalController();
final LayerLink link = LayerLink();
@override
Widget build(BuildContext context) {
return _ControllerInjector(
controller: controller,
child: OverlayPortal(
controller: controller,
child: CompositedTransformTarget(
link: link,
child: IconButton(
onTap: () {
setState(() {
controller.show();
setState(() {});
});
},
child: widget.child,
),
),
overlayChildBuilder: (context) {
// This is a semi-transparent background that will dismiss the
// overlay when tapped.
return GestureDetector(
onTap: () {
setState(() {
controller.hide();
});
},
child: Container(
color: Theme.of(context).colorScheme.shadow,
child: CompositedTransformFollower(
link: link,
targetAnchor: Alignment.bottomRight,
followerAnchor: Alignment.topRight,
// Dummy tappable as a workaround to:
// https://github.com/flutter/flutter/issues/144221
// This prevents the parent gesture detector from dismising the
// overlay when Flutter erroneously calls it when the user
// interrupts scroll momentum by tapping on the list view.
child: GestureDetector(
onTap: () {},
// This is the content of the drop down menu, typically a ListView.
child: widget.overlayBuilder(context),
),
),
),
);
},
),
);
}
}
class _ControllerInjector extends InheritedWidget {
const _ControllerInjector({
required super.child,
required this.controller,
});
final OverlayPortalController controller;
@override
bool updateShouldNotify(_ControllerInjector old) {
return old.controller != controller;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment