expressive_m3 0.2.0
expressive_m3: ^0.2.0 copied to clipboard
Material 3 Expressive design tokens, motion and widgets for Flutter — a shape-morphing loading indicator, pull-to-refresh, wavy progress gauge, spring transitions, expressive shapes and the full M3 ty [...]
import 'package:expressive_m3/expressive_m3.dart';
import 'package:flutter/material.dart';
void main() => runApp(const ExpressiveM3Demo());
class ExpressiveM3Demo extends StatelessWidget {
const ExpressiveM3Demo({super.key});
@override
Widget build(BuildContext context) {
final textTheme = ExpressiveTextTheme.base;
return MaterialApp(
title: 'expressive_m3',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
textTheme: textTheme,
pageTransitionsTheme: expressivePageTransitionsTheme,
extensions: <ThemeExtension<dynamic>>[
EmphasizedTextTheme.of(textTheme),
],
),
home: const _GalleryPage(),
);
}
}
class _GalleryPage extends StatefulWidget {
const _GalleryPage();
@override
State<_GalleryPage> createState() => _GalleryPageState();
}
class _GalleryPageState extends State<_GalleryPage> {
int _done = 2;
static const int _total = 5;
Future<void> _refresh() async {
await Future<void>.delayed(const Duration(seconds: 2));
}
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('expressive_m3')),
floatingActionButton: ExpressiveFabMenu(
items: <ExpressiveFabMenuItem>[
ExpressiveFabMenuItem(
icon: const Icon(Icons.edit),
label: 'Edit',
onPressed: () {},
),
ExpressiveFabMenuItem(
icon: const Icon(Icons.share),
label: 'Share',
onPressed: () {},
),
],
),
body: ExpressiveRefreshIndicator(
onRefresh: _refresh,
child: ListView(
padding: const EdgeInsets.all(ExpressiveSpacing.lg),
children: <Widget>[
const _Section(
title: 'Loading indicator',
child: Row(
spacing: ExpressiveSpacing.xl,
children: <Widget>[
ExpressiveLoadingIndicator(),
ExpressiveLoadingIndicator(contained: true),
],
),
),
_Section(
title: 'Wavy progress',
child: Row(
spacing: ExpressiveSpacing.lg,
children: <Widget>[
CircularWavyProgress(
value: _done / _total,
label: '$_done/$_total',
animationId: 'demo',
),
FilledButton(
onPressed: () => setState(
() => _done = _done >= _total ? 0 : _done + 1,
),
child: const Text('Advance'),
),
],
),
),
_Section(
title: 'Pressable + expressive shape',
child: PressableScale(
child: Material(
color: scheme.secondaryContainer,
shape: const ExpressiveShapeBorder(ExpressiveShape.cookie),
child: const SizedBox(
width: 96,
height: 96,
child: Center(child: Icon(Icons.bolt_rounded)),
),
),
),
),
_Section(
title: 'Button group',
child: ExpressiveButtonGroup(
children: <Widget>[
FilledButton(onPressed: () {}, child: const Text('Day')),
FilledButton(onPressed: () {}, child: const Text('Week')),
FilledButton(onPressed: () {}, child: const Text('Month')),
],
),
),
_Section(
title: 'Split button',
child: ExpressiveSplitButton(
label: const Text('Save'),
onPressed: () {},
menuChildren: <Widget>[
MenuItemButton(
onPressed: () {},
child: const Text('Save as draft'),
),
MenuItemButton(
onPressed: () {},
child: const Text('Save a copy'),
),
],
),
),
_Section(
title: 'Toolbar (floating)',
child: ExpressiveToolbar(
children: <Widget>[
IconButton(onPressed: () {}, icon: const Icon(Icons.undo)),
IconButton(onPressed: () {}, icon: const Icon(Icons.redo)),
IconButton(
onPressed: () {},
icon: const Icon(Icons.format_bold),
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.format_italic),
),
],
),
),
const _Section(
title: 'Wavy progress',
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: ExpressiveSpacing.lg,
children: <Widget>[
ExpressiveWavyLinearProgress(value: 0.6),
ExpressiveWavyLinearProgress(),
Align(
alignment: Alignment.centerLeft,
child:
ExpressiveWavyCircularProgress(value: 0.65, size: 64),
),
],
),
),
_Section(
title: 'Emphasized type + numeric id',
child: Text(
'DR87 NMJ',
style: context.emphasizedText.titleLarge.numericId,
),
),
],
),
),
);
}
}
class _Section extends StatelessWidget {
const _Section({required this.title, required this.child});
final String title;
final Widget child;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: ExpressiveSpacing.xxl),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: ExpressiveSpacing.md,
children: <Widget>[
Text(title, style: Theme.of(context).textTheme.titleMedium),
child,
],
),
);
}
}