Overlay
Persistent Sheet
A persistent sheet is displayed above another widget while still allowing users to interact with the widget below.
It is part of FScaffold, which should be preferred in most cases.
A closely related widget is a modal sheet which prevents the user from interacting with the rest of the app.
All calls to showFPersistentSheet(...) should be made inside widgets that have either FScaffold or FSheets as
their ancestor.
1class _Sheet extends StatefulWidget {2 @override3 State<_Sheet> createState() => _SheetState();4}56class _SheetState extends State<_Sheet> {7 final Map<FLayout, FPersistentSheetController> _controllers = {};89 @override10 void dispose() {11 for (final controller in _controllers.values) {12 controller.dispose();13 }14 super.dispose();15 }1617 @override18 Widget build(BuildContext context) {19 VoidCallback onPress(FLayout side) => () {20 for (final MapEntry(:key, :value) in _controllers.entries) {21 if (key != side && value.status.isCompleted) {22 return;23 }24 }2526 var controller = _controllers[side];27 if (controller == null) {28 controller = _controllers[side] ??= showFPersistentSheet(29 context: context,30 side: side,31 builder: (context, controller) =>32 Form(side: side, controller: controller),33 );34 } else {35 controller.toggle();36 }37 };3839 return Column(40 mainAxisAlignment: .center,41 mainAxisSize: .min,42 spacing: 5,43 children: [44 FButton(onPress: onPress(.ltr), child: const Text('Left')),45 FButton(onPress: onPress(.ttb), child: const Text('Top')),46 FButton(onPress: onPress(.rtl), child: const Text('Right')),47 FButton(onPress: onPress(.btt), child: const Text('Bottom')),48 ],49 );50 }51}5253class Form extends StatelessWidget {54 final FLayout side;55 final FPersistentSheetController controller;56 const Form({required this.side, required this.controller, super.key});57 @override58 Widget build(BuildContext context) => Container(59 height: .infinity,60 width: .infinity,61 decoration: BoxDecoration(62 color: context.theme.colors.background,63 border: side.vertical64 ? .symmetric(65 horizontal: BorderSide(color: context.theme.colors.border),66 )67 : .symmetric(68 vertical: BorderSide(color: context.theme.colors.border),69 ),70 ),71 child: Padding(72 padding: const .symmetric(horizontal: 15, vertical: 8.0),73 child: Column(74 mainAxisAlignment: .center,75 mainAxisSize: .min,76 crossAxisAlignment: .start,77 children: [78 Text(79 'Account',80 style: context.theme.typography.xl2.copyWith(81 fontWeight: .w600,82 color: context.theme.colors.foreground,83 height: 1.5,84 ),85 ),86 Text(87 'Make changes to your account here. Click save when you are done.',88 style: context.theme.typography.sm.copyWith(89 color: context.theme.colors.mutedForeground,90 ),91 ),92 const SizedBox(height: 8),93 SizedBox(94 width: 450,95 child: Column(96 children: [97 const FTextField(label: Text('Name'), hint: 'John Renalo'),98 const SizedBox(height: 10),99 const FTextField(label: Text('Email'), hint: 'john@doe.com'),100 const SizedBox(height: 16),101 FButton(onPress: controller.toggle, child: const Text('Save')),102 ],103 ),104 ),105 ],106 ),107 ),108 );109}110CLI
To generate and customize this style:
dart run forui style create persistent-sheetUsage
showFPersistentSheet(...)
1showFPersistentSheet(2 context: context,3 style: const .delta(flingVelocity: 700),4 side: .btt,5 builder: (context, controller) => Padding(6 padding: const .all(16),7 child: Column(8 mainAxisSize: .min,9 children: [10 const Text('Sheet content'),11 FButton(onPress: controller.hide, child: const Text('Close')),12 ],13 ),14 ),15)FSheets(...)
1FSheets(2 child: Placeholder(),3)Examples
With KeepAliveOffstage
1class _Sheet extends StatefulWidget {2 @override3 State<_Sheet> createState() => _SheetState();4}56class _SheetState extends State<_Sheet> {7 final Map<FLayout, FPersistentSheetController> _controllers = {};89 @override10 void dispose() {11 for (final controller in _controllers.values) {12 controller.dispose();13 }14 super.dispose();15 }1617 @override18 Widget build(BuildContext context) {19 VoidCallback onPress(FLayout side) => () {20 for (final MapEntry(:key, :value) in _controllers.entries) {21 if (key != side && value.status.isCompleted) {22 return;23 }24 }2526 var controller = _controllers[side];27 if (controller == null) {28 controller = _controllers[side] ??= showFPersistentSheet(29 context: context,30 side: side,31 keepAliveOffstage: true,32 builder: (context, controller) =>33 Form(side: side, controller: controller),34 );35 } else {36 controller.toggle();37 }38 };3940 return Column(41 mainAxisAlignment: .center,42 mainAxisSize: .min,43 spacing: 5,44 children: [45 FButton(onPress: onPress(.ltr), child: const Text('Left')),46 FButton(onPress: onPress(.ttb), child: const Text('Top')),47 FButton(onPress: onPress(.rtl), child: const Text('Right')),48 FButton(onPress: onPress(.btt), child: const Text('Bottom')),49 ],50 );51 }52}5354class Form extends StatelessWidget {55 final FLayout side;56 final FPersistentSheetController controller;57 const Form({required this.side, required this.controller, super.key});58 @override59 Widget build(BuildContext context) => Container(60 height: .infinity,61 width: .infinity,62 decoration: BoxDecoration(63 color: context.theme.colors.background,64 border: side.vertical65 ? .symmetric(66 horizontal: BorderSide(color: context.theme.colors.border),67 )68 : .symmetric(69 vertical: BorderSide(color: context.theme.colors.border),70 ),71 ),72 child: Padding(73 padding: const .symmetric(horizontal: 15, vertical: 8.0),74 child: Column(75 mainAxisAlignment: .center,76 mainAxisSize: .min,77 crossAxisAlignment: .start,78 children: [79 Text(80 'Account',81 style: context.theme.typography.xl2.copyWith(82 fontWeight: .w600,83 color: context.theme.colors.foreground,84 height: 1.5,85 ),86 ),87 Text(88 'Make changes to your account here. Click save when you are done.',89 style: context.theme.typography.sm.copyWith(90 color: context.theme.colors.mutedForeground,91 ),92 ),93 const SizedBox(height: 8),94 SizedBox(95 width: 450,96 child: Column(97 children: [98 const FTextField(label: Text('Name'), hint: 'John Renalo'),99 const SizedBox(height: 10),100 const FTextField(label: Text('Email'), hint: 'john@doe.com'),101 const SizedBox(height: 16),102 FButton(onPress: controller.toggle, child: const Text('Save')),103 ],104 ),105 ),106 ],107 ),108 ),109 );110}111With DraggableScrollableSheet
1class DraggablePersistentSheetExample extends StatefulWidget {2 @override3 State<DraggablePersistentSheetExample> createState() => _DraggableState();4}56class _DraggableState extends State<DraggablePersistentSheetExample> {7 FPersistentSheetController? controller;89 @override10 void dispose() {11 controller?.dispose();12 super.dispose();13 }1415 @override16 Widget build(BuildContext context) => FButton(17 child: const Text('Click me'),18 onPress: () {19 if (controller != null) {20 controller!.toggle();21 return;22 }2324 controller = showFPersistentSheet(25 context: context,26 side: .btt,27 mainAxisMaxRatio: null,28 builder: (context, _) => DraggableScrollableSheet(29 expand: false,30 builder: (context, controller) => ScrollConfiguration(31 // This is required to enable dragging on desktop.32 // See https://github.com/flutter/flutter/issues/101903 for more information.33 behavior: ScrollConfiguration.of(34 context,35 ).copyWith(dragDevices: {.touch, .mouse, .trackpad}),36 child: FTileGroup.builder(37 count: 25,38 scrollController: controller,39 tileBuilder: (context, index) =>40 FTile(title: Text('Tile $index')),41 ),42 ),43 ),44 );45 },46 );47}48