modal_top_sheet 1.0.0
modal_top_sheet: ^1.0.0 copied to clipboard
Modal sheets that slide in from the top, like an app bar dropdown. Awaitable, dismissible, with a configurable barrier, curves and duration.
import 'package:flutter/material.dart';
import 'package:modal_top_sheet/modal_top_sheet.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'modal_top_sheet example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static const List<String> _languages = <String>[
'Polski',
'English',
'Deutsch',
];
String _language = 'Polski';
/// The sheet returns what the user picked, like any other Flutter modal.
Future<void> _pickLanguage() async {
final String? picked = await showModalTopSheet<String>(
context,
child: _LanguageSheet(languages: _languages, selected: _language),
);
if (picked != null && mounted) {
setState(() => _language = picked);
}
}
Future<void> _showBlocking() => showModalTopSheet<void>(
context,
// Neither a tap on the barrier nor a system back closes this one.
isDismissible: false,
barrierColor: Colors.black54,
child: Builder(
builder: (BuildContext context) => _Sheet(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('This one can only be closed from inside.'),
const SizedBox(height: 12),
FilledButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Close'),
),
],
),
),
),
);
Future<void> _showSlow() => showModalTopSheet<void>(
context,
duration: const Duration(milliseconds: 900),
curve: Curves.elasticOut,
reverseCurve: Curves.easeInBack,
padding: const EdgeInsets.only(top: 120),
barrierColor: Colors.indigo.withValues(alpha: 0.3),
child: const _Sheet(child: Text('Slower, springier, lower.')),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('modal_top_sheet')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Language: $_language',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 24),
FilledButton(
onPressed: _pickLanguage,
child: const Text('Pick a language'),
),
const SizedBox(height: 12),
OutlinedButton(
onPressed: _showBlocking,
child: const Text('Non-dismissible sheet'),
),
const SizedBox(height: 12),
OutlinedButton(
onPressed: _showSlow,
child: const Text('Custom curve and padding'),
),
],
),
),
);
}
}
/// A card the sheets share, so the demo shows content rather than chrome.
class _Sheet extends StatelessWidget {
const _Sheet({required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: Padding(padding: const EdgeInsets.all(24), child: child),
);
}
}
class _LanguageSheet extends StatelessWidget {
const _LanguageSheet({required this.languages, required this.selected});
final List<String> languages;
final String selected;
@override
Widget build(BuildContext context) {
return _Sheet(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
for (final String language in languages)
ListTile(
title: Text(language),
trailing: language == selected ? const Icon(Icons.check) : null,
onTap: () => Navigator.of(context).pop(language),
),
],
),
);
}
}