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 Row(40 mainAxisAlignment: .center,41 mainAxisSize: .min,42 spacing: 10,43 children: [44 FButton(45 variant: .outline,46 size: .sm,47 mainAxisSize: .min,48 onPress: onPress(.ltr),49 child: const Text('Left'),50 ),51 FButton(52 variant: .outline,53 size: .sm,54 mainAxisSize: .min,55 onPress: onPress(.ttb),56 child: const Text('Top'),57 ),58 FButton(59 variant: .outline,60 size: .sm,61 mainAxisSize: .min,62 onPress: onPress(.btt),63 child: const Text('Bottom'),64 ),65 FButton(66 variant: .outline,67 size: .sm,68 mainAxisSize: .min,69 onPress: onPress(.rtl),70 child: const Text('Right'),71 ),72 ],73 );74 }75}7677class Form extends StatelessWidget {78 final FLayout side;79 final FPersistentSheetController controller;80 const Form({required this.side, required this.controller, super.key});81 @override82 Widget build(BuildContext context) => Container(83 height: .infinity,84 width: .infinity,85 decoration: BoxDecoration(86 color: context.theme.colors.background,87 border: side.vertical88 ? .symmetric(89 horizontal: BorderSide(color: context.theme.colors.border),90 )91 : .symmetric(92 vertical: BorderSide(color: context.theme.colors.border),93 ),94 ),95 child: Center(96 child: Padding(97 padding: const .symmetric(horizontal: 15, vertical: 8.0),98 child: Column(99 mainAxisAlignment: .center,100 mainAxisSize: .min,101 crossAxisAlignment: .start,102 children: [103 Text(104 'Account',105 style: context.theme.typography.xl2.copyWith(106 fontWeight: .w600,107 color: context.theme.colors.foreground,108 height: 1.5,109 ),110 ),111 Text(112 'Make changes to your account here. Click save when you are done.',113 style: context.theme.typography.sm.copyWith(114 color: context.theme.colors.mutedForeground,115 ),116 ),117 const SizedBox(height: 8),118 SizedBox(119 width: 450,120 child: Column(121 children: [122 const FTextField(label: Text('Name'), hint: 'John Renalo'),123 const SizedBox(height: 10),124 const FTextField(label: Text('Email'), hint: 'john@doe.com'),125 const SizedBox(height: 16),126 FButton(127 onPress: controller.toggle,128 child: const Text('Save'),129 ),130 ],131 ),132 ),133 ],134 ),135 ),136 ),137 );138}139CLI
To generate a specific style for customization:
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 Row(41 mainAxisAlignment: .center,42 mainAxisSize: .min,43 spacing: 10,44 children: [45 FButton(46 variant: .outline,47 size: .sm,48 mainAxisSize: .min,49 onPress: onPress(.ltr),50 child: const Text('Left'),51 ),52 FButton(53 variant: .outline,54 size: .sm,55 mainAxisSize: .min,56 onPress: onPress(.ttb),57 child: const Text('Top'),58 ),59 FButton(60 variant: .outline,61 size: .sm,62 mainAxisSize: .min,63 onPress: onPress(.btt),64 child: const Text('Bottom'),65 ),66 FButton(67 variant: .outline,68 size: .sm,69 mainAxisSize: .min,70 onPress: onPress(.rtl),71 child: const Text('Right'),72 ),73 ],74 );75 }76}7778class Form extends StatelessWidget {79 final FLayout side;80 final FPersistentSheetController controller;81 const Form({required this.side, required this.controller, super.key});82 @override83 Widget build(BuildContext context) => Container(84 height: .infinity,85 width: .infinity,86 decoration: BoxDecoration(87 color: context.theme.colors.background,88 border: side.vertical89 ? .symmetric(90 horizontal: BorderSide(color: context.theme.colors.border),91 )92 : .symmetric(93 vertical: BorderSide(color: context.theme.colors.border),94 ),95 ),96 child: Center(97 child: Padding(98 padding: const .symmetric(horizontal: 15, vertical: 8.0),99 child: Column(100 mainAxisAlignment: .center,101 mainAxisSize: .min,102 crossAxisAlignment: .start,103 children: [104 Text(105 'Account',106 style: context.theme.typography.xl2.copyWith(107 fontWeight: .w600,108 color: context.theme.colors.foreground,109 height: 1.5,110 ),111 ),112 Text(113 'Make changes to your account here. Click save when you are done.',114 style: context.theme.typography.sm.copyWith(115 color: context.theme.colors.mutedForeground,116 ),117 ),118 const SizedBox(height: 8),119 SizedBox(120 width: 450,121 child: Column(122 children: [123 const FTextField(label: Text('Name'), hint: 'John Renalo'),124 const SizedBox(height: 10),125 const FTextField(label: Text('Email'), hint: 'john@doe.com'),126 const SizedBox(height: 16),127 FButton(128 onPress: controller.toggle,129 child: const Text('Save'),130 ),131 ],132 ),133 ),134 ],135 ),136 ),137 ),138 );139}140With 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 variant: .outline,18 size: .sm,19 mainAxisSize: .min,20 child: const Text('Click me'),21 onPress: () {22 if (controller != null) {23 controller!.toggle();24 return;25 }2627 controller = showFPersistentSheet(28 context: context,29 side: .btt,30 mainAxisMaxRatio: null,31 builder: (context, _) => DraggableScrollableSheet(32 expand: false,33 builder: (context, controller) => ScrollConfiguration(34 // This is required to enable dragging on desktop.35 // See https://github.com/flutter/flutter/issues/101903 for more information.36 behavior: ScrollConfiguration.of(37 context,38 ).copyWith(dragDevices: {.touch, .mouse, .trackpad}),39 child: FTileGroup.builder(40 count: 25,41 scrollController: controller,42 tileBuilder: (context, index) =>43 FTile(title: Text('Tile $index')),44 ),45 ),46 ),47 );48 },49 );50}51