Popover
A popover displays rich content in a portal that is aligned to a child.
Preview
Code
class _Popover extends StatelessWidget {
@override
Widget build(BuildContext context) => FPopover(
controller: controller,
popoverAnchor: Alignment.bottomLeft,
childAnchor: Alignment.bottomRight,
popoverBuilder: (context, controller) => Padding(
padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
child: SizedBox(
width: 288,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Dimensions', style: context.theme.typography.base),
const SizedBox(height: 7),
Text(
'Set the dimensions for the layer.',
style: context.theme.typography.sm.copyWith(
color: context.theme.colors.mutedForeground,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 15),
for (final (label, value) in [
('Width', '100%'),
('Max. Width', '300px'),
('Height', '25px'),
('Max. Height', 'none'),
]) ...[
Row(
children: [
Expanded(child: Text(label, style: context.theme.typography.sm)),
Expanded(flex: 2, child: FTextField(initialText: value)),
],
),
const SizedBox(height: 7),
],
],
),
),
),
builder: (context, controller, child) => IntrinsicWidth(
child: FButton(style: FButtonStyle.outline, onPress: controller.toggle, child: const Text('Open popover')),
),
);
}
CLI
To generate and customize this style:
dart run forui style create popover
Usage
FPopover(...)
FPopover(
controller: FPopoverController(vsync: this),
style: FPopoverStyle(...),
popoverAnchor: Alignment.topCenter,
childAnchor: Alignment.bottomCenter,
constraints: const FPortalConstraints(),
spacing: const FPortalSpacing(4),
shift: FPortalShift.flip,
offset: Offset.zero,
groupId: 'popover-group',
hideOnTapOutside: FHidePopoverRegion.excludeTarget,
popoverBuilder: (context, controller) => const Placeholder(),
builder: (context, controller, child) => const Placeholder(),
child: const Placeholder(),
);
Examples
Horizontal Alignment
You can change how the popover is aligned to the button.
Preview
Code
class _Popover extends StatelessWidget {
@override
Widget build(BuildContext context) => FPopover(
controller: controller,
popoverAnchor: Alignment.bottomLeft,
childAnchor: Alignment.bottomRight,
popoverBuilder: (context, controller) => Padding(
padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
child: SizedBox(
width: 288,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Dimensions', style: context.theme.typography.base),
const SizedBox(height: 7),
Text(
'Set the dimensions for the layer.',
style: context.theme.typography.sm.copyWith(
color: context.theme.colors.mutedForeground,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 15),
for (final (label, value) in [
('Width', '100%'),
('Max. Width', '300px'),
('Height', '25px'),
('Max. Height', 'none'),
]) ...[
Row(
children: [
Expanded(child: Text(label, style: context.theme.typography.sm)),
Expanded(flex: 2, child: FTextField(initialText: value)),
],
),
const SizedBox(height: 7),
],
],
),
),
),
builder: (context, controller, child) => IntrinsicWidth(
child: FButton(style: FButtonStyle.outline, onPress: controller.toggle, child: const Text('Open popover')),
),
);
}
Tapping Outside Does Not Close Popover
Preview
Code
class _Popover extends StatelessWidget {
@override
Widget build(BuildContext context) => FPopover(
controller: controller,
hideOnTapOutside: FHidePopoverRegion.none,
popoverBuilder: (context, style) => Padding(
padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
child: SizedBox(
width: 288,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Dimensions', style: context.theme.typography.base),
const SizedBox(height: 7),
Text(
'Set the dimensions for the layer.',
style: context.theme.typography.sm.copyWith(
color: context.theme.colors.mutedForeground,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 15),
for (final (label, value) in [
('Width', '100%'),
('Max. Width', '300px'),
('Height', '25px'),
('Max. Height', 'none'),
]) ...[
Row(
children: [
Expanded(child: Text(label, style: context.theme.typography.sm)),
Expanded(flex: 2, child: FTextField(initialText: value)),
],
),
const SizedBox(height: 7),
],
],
),
),
),
builder: (context, controller, child) => IntrinsicWidth(
child: FButton(style: FButtonStyle.outline, onPress: controller.toggle, child: const Text('Open popover')),
),
);
}
Blurred Barrier
Preview
Code
class BlurredPopoverPage extends StatelessWidget {
@override
Widget build(BuildContext context) => Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Layer Properties', style: context.theme.typography.xl.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
const FTextField(initialText: 'Header Component'),
const SizedBox(height: 16),
const FTextField(initialText: 'Navigation Bar'),
const SizedBox(height: 30),
],
),
FPopover(
style: context.theme.popoverStyle.copyWith(
barrierFilter: (animation) => ImageFilter.compose(
outer: ImageFilter.blur(sigmaX: animation * 5, sigmaY: animation * 5),
inner: ColorFilter.mode(
Color.lerp(Colors.transparent, Colors.black.withValues(alpha: 0.2), animation)!,
BlendMode.srcOver,
),
),
popoverAnchor: Alignment.topCenter,
childAnchor: Alignment.bottomCenter,
popoverBuilder: (context, controller) => Padding(
padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
child: SizedBox(
width: 288,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Dimensions', style: context.theme.typography.base),
const SizedBox(height: 7),
Text(
'Set the dimensions for the layer.',
style: context.theme.typography.sm.copyWith(
color: context.theme.colors.mutedForeground,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 15),
for (final (label, value) in [
('Width', '100%'),
('Max. Width', '300px'),
('Height', '25px'),
('Max. Height', 'none'),
]) ...[
Row(
children: [
Expanded(child: Text(label, style: context.theme.typography.sm)),
Expanded(flex: 2, child: FTextField(initialText: value)),
],
),
const SizedBox(height: 7),
],
],
),
),
),
builder: (context, controller, child) => IntrinsicWidth(
child: FButton(style: FButtonStyle.outline, onPress: controller.toggle, child: const Text('Open popover')),
),
),
],
);
}
Flip along Axis
The popover can be flipped along the overflowing axis to stay within the viewport boundaries.
Preview
Code
class _Popover extends StatelessWidget {
@override
Widget build(BuildContext context) => FPopover(
controller: controller,
shift: FPortalShift.flip,
popoverBuilder: (context, _) => Padding(
padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
child: SizedBox(
width: 288,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Dimensions', style: context.theme.typography.base),
const SizedBox(height: 7),
Text(
'Set the dimensions for the layer.',
style: context.theme.typography.sm.copyWith(
color: context.theme.colors.mutedForeground,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 15),
for (final (label, value) in [
('Width', '100%'),
('Max. Width', '300px'),
('Height', '25px'),
('Max. Height', 'none'),
]) ...[
Row(
children: [
Expanded(child: Text(label, style: context.theme.typography.sm)),
Expanded(flex: 2, child: FTextField(initialText: value)),
],
),
const SizedBox(height: 7),
],
],
),
),
),
builder: (context, controller, child) => IntrinsicWidth(
child: FButton(style: FButtonStyle.outline, onPress: controller.toggle, child: const Text('Open popover')),
),
);
}
Shift along Axis
The popover can be shifted along the overflowing axis to stay within the viewport boundaries.
Preview
Code
class _Popover extends StatelessWidget {
@override
Widget build(BuildContext context) => FPopover(
controller: controller,
shift: FPortalShift.along,
popoverBuilder: (context, controller) => Padding(
padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
child: SizedBox(
width: 288,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Dimensions', style: context.theme.typography.base),
const SizedBox(height: 7),
Text(
'Set the dimensions for the layer.',
style: context.theme.typography.sm.copyWith(
color: context.theme.colors.mutedForeground,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 15),
for (final (label, value) in [
('Width', '100%'),
('Max. Width', '300px'),
('Height', '25px'),
('Max. Height', 'none'),
]) ...[
Row(
children: [
Expanded(child: Text(label, style: context.theme.typography.sm)),
Expanded(flex: 2, child: FTextField(initialText: value)),
],
),
const SizedBox(height: 7),
],
],
),
),
),
builder: (context, controller, child) => IntrinsicWidth(
child: FButton(style: FButtonStyle.outline, onPress: controller.toggle, child: const Text('Open popover')),
),
);
}
No Shifting
The popover is not shifted to stay within the viewport boundaries, even if it overflows.
Preview
Code
class _Popover extends StatelessWidget {
@override
Widget build(BuildContext context) => FPopover(
controller: controller,
shift: FPortalShift.none,
popoverBuilder: (context, controller) => Padding(
padding: const EdgeInsets.only(left: 20, top: 14, right: 20, bottom: 10),
child: SizedBox(
width: 288,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Dimensions', style: context.theme.typography.base),
const SizedBox(height: 7),
Text(
'Set the dimensions for the layer.',
style: context.theme.typography.sm.copyWith(
color: context.theme.colors.mutedForeground,
fontWeight: FontWeight.w300,
),
),
const SizedBox(height: 15),
for (final (label, value) in [
('Width', '100%'),
('Max. Width', '300px'),
('Height', '25px'),
('Max. Height', 'none'),
]) ...[
Row(
children: [
Expanded(child: Text(label, style: context.theme.typography.sm)),
Expanded(flex: 2, child: FTextField(initialText: value)),
],
),
const SizedBox(height: 7),
],
],
),
),
),
builder: (context, controller, child) => IntrinsicWidth(
child: FButton(style: FButtonStyle.outline, onPress: controller.toggle, child: const Text('Open popover')),
),
);
}
Last updated on