Refactor button component and add new sections for loading indicators, icons, and navigation; update enums and pubspec description

This commit is contained in:
Emily Pauli 2025-10-21 23:40:25 +02:00
commit 020db0ac38
23 changed files with 1033 additions and 828 deletions

View file

@ -1,5 +1,15 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'package:m3e_gallery/sections/button_section.dart';
import 'package:m3e_gallery/sections/fab_section.dart';
import 'package:m3e_gallery/sections/icon_button_section.dart';
import 'package:m3e_gallery/sections/loading_indicator_section.dart';
import 'package:m3e_gallery/sections/navigation_section.dart';
import 'package:m3e_gallery/sections/progress_section.dart';
import 'package:m3e_gallery/sections/section_card.dart';
import 'package:m3e_gallery/sections/slider_section.dart';
import 'package:m3e_gallery/sections/split_button_section.dart';
import 'package:m3e_gallery/sections/toolbar_section.dart';
void main() => runApp(const GalleryApp());
@ -26,19 +36,8 @@ class GalleryHome extends StatefulWidget {
}
class _GalleryHomeState extends State<GalleryHome> {
// Navigation examples
int _navBarIndex = 0;
int _railIndex = 0;
// Slider examples
double _sliderValue = 0.4;
RangeValues _rangeValues = const RangeValues(0.25, 0.75);
// Progress examples
double _progressValue = 0.6;
void onPressed() {
// Placeholder function for button onPressed
// Placeholder function onPressed
}
@override
@ -55,478 +54,7 @@ class _GalleryHomeState extends State<GalleryHome> {
Icon(Icons.more_vert),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// Icon buttons
Text('IconButtonM3E', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
IconButtonM3E(
icon: Icon(Icons.favorite),
variant: IconButtonM3EVariant.filled,
onPressed: onPressed),
IconButtonM3E(
icon: Icon(Icons.favorite),
variant: IconButtonM3EVariant.tonal,
onPressed: onPressed),
IconButtonM3E(
icon: Icon(Icons.favorite),
variant: IconButtonM3EVariant.outlined,
onPressed: onPressed),
IconButtonM3E(
icon: Icon(Icons.favorite),
variant: IconButtonM3EVariant.standard,
onPressed: onPressed),
],
),
const SizedBox(height: 24),
// Split Button
Text('SplitButtonM3E', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
SplitButtonM3E<String>(
label: 'Primary',
onPressed: () {},
items: const [
SplitButtonM3EItem<String>(value: 'one', child: 'One'),
SplitButtonM3EItem<String>(value: 'two', child: 'Two'),
],
),
SplitButtonM3E<String>(
label: 'Tonal',
emphasis: SplitButtonM3EEmphasis.tonal,
onPressed: () {},
items: const [
SplitButtonM3EItem<String>(value: 'one', child: 'One'),
SplitButtonM3EItem<String>(value: 'two', child: 'Two'),
],
),
SplitButtonM3E<String>(
label: 'Outlined',
emphasis: SplitButtonM3EEmphasis.outlined,
onPressed: () {},
items: const [
SplitButtonM3EItem<String>(value: 'one', child: 'One'),
SplitButtonM3EItem<String>(value: 'two', child: 'Two'),
],
),
SplitButtonM3E<String>(
label: 'Text',
emphasis: SplitButtonM3EEmphasis.text,
onPressed: () {},
items: const [
SplitButtonM3EItem<String>(value: 'one', child: 'One'),
SplitButtonM3EItem<String>(value: 'two', child: 'Two'),
],
),
],
),
const SizedBox(height: 24),
// Buttons
Text('ButtonM3E', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Wrap(spacing: 12, runSpacing: 12, children: [
Wrap(
spacing: 12,
runSpacing: 12,
children: [
ButtonM3E(
labelText: 'Filled',
variant: ButtonM3EVariant.filled,
onPressed: onPressed),
ButtonM3E(
labelText: 'Tonal',
variant: ButtonM3EVariant.tonal,
onPressed: onPressed),
ButtonM3E(
labelText: 'Outlined',
variant: ButtonM3EVariant.outlined,
onPressed: onPressed),
ButtonM3E(
labelText: 'Text',
variant: ButtonM3EVariant.text,
onPressed: onPressed),
ButtonM3E(
labelText: 'Elevated',
variant: ButtonM3EVariant.elevated,
onPressed: onPressed),
],
),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
ButtonM3E(
labelText: 'Filled',
variant: ButtonM3EVariant.filled,
shapeFamily: ButtonM3EShapeFamily.square,
onPressed: onPressed),
ButtonM3E(
labelText: 'Tonal',
variant: ButtonM3EVariant.tonal,
shapeFamily: ButtonM3EShapeFamily.square,
onPressed: onPressed),
ButtonM3E(
labelText: 'Outlined',
variant: ButtonM3EVariant.outlined,
shapeFamily: ButtonM3EShapeFamily.square,
onPressed: onPressed),
ButtonM3E(
labelText: 'Text',
variant: ButtonM3EVariant.text,
shapeFamily: ButtonM3EShapeFamily.square,
onPressed: onPressed),
ButtonM3E(
labelText: 'Elevated',
variant: ButtonM3EVariant.elevated,
shapeFamily: ButtonM3EShapeFamily.square,
onPressed: onPressed),
],
),
]),
const SizedBox(height: 24),
// Button groups
Text('ButtonGroupM3E', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
// Standard group, round, md
ButtonGroupM3E(
type: ButtonGroupM3EType.standard,
shape: ButtonGroupM3EShape.round,
size: ButtonGroupM3ESize.md,
children: const [
IconButtonM3E(icon: Icon(Icons.skip_previous)),
IconButtonM3E(icon: Icon(Icons.play_arrow)),
IconButtonM3E(icon: Icon(Icons.skip_next)),
],
),
const SizedBox(height: 12),
// Connected group with divider and clipping for outer corners
ButtonGroupM3E(
type: ButtonGroupM3EType.connected,
shape: ButtonGroupM3EShape.round,
size: ButtonGroupM3ESize.lg,
showDividers: true,
clipBehavior: Clip.hardEdge,
equalizeWidths: true,
semanticLabel: 'Actions',
children: [
SplitButtonM3E<String>(
label: 'Primary',
onPressed: () {},
items: const [
SplitButtonM3EItem<String>(value: 'one', child: 'One'),
SplitButtonM3EItem<String>(value: 'two', child: 'Two'),
],
),
SplitButtonM3E<String>(
label: 'Tonal',
emphasis: SplitButtonM3EEmphasis.tonal,
onPressed: () {},
items: const [
SplitButtonM3EItem<String>(value: 'one', child: 'One'),
SplitButtonM3EItem<String>(value: 'two', child: 'Two'),
],
),
],
),
const SizedBox(height: 12),
// Square, compact, vertical wrap
ButtonGroupM3E(
type: ButtonGroupM3EType.standard,
shape: ButtonGroupM3EShape.square,
size: ButtonGroupM3ESize.sm,
density: ButtonGroupM3EDensity.compact,
direction: Axis.vertical,
children: const [
IconButtonM3E(icon: Icon(Icons.view_agenda_outlined)),
IconButtonM3E(icon: Icon(Icons.table_rows_outlined)),
IconButtonM3E(icon: Icon(Icons.grid_view_outlined)),
],
),
const SizedBox(height: 24),
// Toolbar
Text('ToolbarM3E', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
ToolbarM3E(
titleText: 'Toolbar Title',
subtitleText: 'Subtitle',
actions: [
ToolbarActionM3E(icon: Icons.search, onPressed: () {}),
ToolbarActionM3E(icon: Icons.share, onPressed: () {}),
ToolbarActionM3E(
icon: Icons.delete,
onPressed: () {},
isDestructive: true,
label: 'Delete'),
ToolbarActionM3E(
icon: Icons.settings, onPressed: () {}, label: 'Settings'),
],
maxInlineActions: 2,
),
const SizedBox(height: 24),
// FABs
Text('FabM3E', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: const [
FabM3E(icon: Icon(Icons.add)),
FabM3E(icon: Icon(Icons.edit), kind: FabM3EKind.secondary),
FabM3E(icon: Icon(Icons.share), kind: FabM3EKind.tertiary),
FabM3E(icon: Icon(Icons.more_horiz), kind: FabM3EKind.surface),
FabM3E(icon: Icon(Icons.add), size: FabM3ESize.small),
FabM3E(icon: Icon(Icons.add), size: FabM3ESize.large),
],
),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: const [
ExtendedFabM3E(label: Text('Create'), icon: Icon(Icons.add)),
ExtendedFabM3E(
label: Text('Edit'),
icon: Icon(Icons.edit),
kind: FabM3EKind.secondary),
ExtendedFabM3E(
label: Text('Share'),
icon: Icon(Icons.share),
kind: FabM3EKind.tertiary),
],
),
const SizedBox(height: 12),
SizedBox(
height: 180,
child: Stack(
children: [
Positioned.fill(
child: Container(
decoration: BoxDecoration(
color:
Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: m3e.shapes.round.lg,
),
),
),
Align(
alignment: Alignment.bottomRight,
child: FabMenuM3E(
primaryFab: const FabM3E(icon: Icon(Icons.menu)),
items: [
FabMenuItem(
icon: const Icon(Icons.image),
label: const Text('Image'),
onPressed: () {}),
FabMenuItem(
icon: const Icon(Icons.camera_alt),
label: const Text('Camera'),
onPressed: () {}),
FabMenuItem(
icon: const Icon(Icons.file_upload),
label: const Text('Upload'),
onPressed: () {}),
],
),
),
],
),
),
const SizedBox(height: 24),
// Loading indicators
Text('LoadingIndicatorM3E',
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Wrap(
spacing: 16,
runSpacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
children: const [
LoadingIndicatorM3E(
variant: LoadingIndicatorM3EVariant.defaultStyle),
LoadingIndicatorM3E(
variant: LoadingIndicatorM3EVariant.contained),
],
),
const SizedBox(height: 24),
// Progress indicators
Text('ProgressIndicatorM3E',
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Wrap(
spacing: 16,
runSpacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
const CircularProgressM3E(),
const CircularProgressM3E(size: ProgressM3ESize.small),
CircularProgressM3E(
size: ProgressM3ESize.large,
value: _progressValue,
showCenterLabel: true),
const LinearProgressM3E(minWidth: 200),
LinearProgressM3E(minWidth: 200, value: _progressValue),
const LinearProgressM3E(
minWidth: 200,
variant: LinearProgressM3EVariant.indeterminate),
const LinearProgressM3E(
minWidth: 200, variant: LinearProgressM3EVariant.query),
const LinearProgressM3E(
minWidth: 200,
variant: LinearProgressM3EVariant.buffer,
bufferValue: 0.8,
value: 0.4),
],
),
const SizedBox(height: 24),
// Sliders
Text('SliderM3E', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
SliderM3E(
value: _sliderValue,
onChanged: (v) => setState(() => _sliderValue = v),
min: 0,
max: 100,
label: '${_sliderValue.toStringAsFixed(0)}',
startIcon: const Icon(Icons.volume_mute),
endIcon: const Icon(Icons.volume_up),
),
const SizedBox(height: 8),
RangeSliderM3E(
values: _rangeValues,
onChanged: (v) => setState(() => _rangeValues = v),
min: 0,
max: 100,
labels: RangeLabels(
_rangeValues.start.toStringAsFixed(0),
_rangeValues.end.toStringAsFixed(0),
),
),
const SizedBox(height: 24),
// Navigation Bar
Text('NavigationBarM3E',
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
NavigationBarM3E(
selectedIndex: _navBarIndex,
onDestinationSelected: (i) => setState(() => _navBarIndex = i),
indicatorStyle: NavBarM3EIndicatorStyle.pill,
destinations: const [
NavigationDestinationM3E(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home'),
NavigationDestinationM3E(
icon: Icon(Icons.search_outlined),
selectedIcon: Icon(Icons.search),
label: 'Search',
badgeDot: true),
NavigationDestinationM3E(
icon: Icon(Icons.favorite_outline),
selectedIcon: Icon(Icons.favorite),
label: 'Favorites',
badgeCount: 3),
NavigationDestinationM3E(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: 'Profile'),
],
),
const SizedBox(height: 24),
// Navigation Rail
Text('NavigationRailM3E',
style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: m3e.shapes.round.lg,
),
height: 180,
child: Row(
children: [
NavigationRailM3E(
selectedIndex: _railIndex,
onDestinationSelected: (i) => setState(() => _railIndex = i),
indicatorStyle: RailIndicatorStyle.pill,
destinations: const [
RailDestinationM3E(
icon: Icon(Icons.dashboard_outlined),
selectedIcon: Icon(Icons.dashboard),
label: 'Dashboard'),
RailDestinationM3E(
icon: Icon(Icons.analytics_outlined),
selectedIcon: Icon(Icons.analytics),
label: 'Reports'),
RailDestinationM3E(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: 'Settings'),
],
),
const VerticalDivider(width: 1),
Expanded(
child: Center(
child: Text('Selected: $_railIndex'),
),
),
],
),
),
const SizedBox(height: 24),
// Sliver App Bar demo route
Text('App bars', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Wrap(
spacing: 12,
children: [
const ButtonM3E(labelText: 'Open SliverAppBarM3E Demo'),
// Use GestureDetector to navigate when tapping the button label area
]
.map((w) => GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const _SliverAppBarDemoPage()),
),
child: w,
))
.toList(),
),
const SizedBox(height: 48),
// Theming surface example remains
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: m3e.colors.surfaceStrong,
borderRadius: m3e.shapes.square.lg,
),
child: Text('Surface strong example',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(color: cs.onSurface)),
),
],
),
body: const SectionedGallery(),
);
}
}
@ -558,3 +86,38 @@ class _SliverAppBarDemoPage extends StatelessWidget {
);
}
}
class SectionedGallery extends StatelessWidget {
const SectionedGallery({super.key});
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
const IconButtonSection(),
const SplitButtonSection(),
const ButtonSection(),
const ToolbarSection(),
const FabSection(),
const LoadingIndicatorSection(),
const ProgressSection(),
const SliderSection(),
const NavigationSection(),
SectionCard(
title: 'App bars',
child: Align(
alignment: Alignment.centerLeft,
child: ButtonM3E(
label: Text('Open SliverAppBarM3E Demo'),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const _SliverAppBarDemoPage()),
),
),
),
),
],
);
}
}

View file

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'section_card.dart';
class ButtonSection extends StatelessWidget {
const ButtonSection({super.key});
@override
Widget build(BuildContext context) {
return SectionCard(
title: 'ButtonM3E',
subtitle:
'Generated from enums: variant × size; grouped by shape family.',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final shape in ButtonM3EShape.values) ...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text('${shape.name} shape',
style: Theme.of(context).textTheme.titleMedium),
),
for (final size in ButtonM3ESize.values) ...[
Padding(
padding: const EdgeInsets.only(top: 8, bottom: 4),
child: Text('size: ${size.name}',
style: Theme.of(context).textTheme.labelLarge),
),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
for (final variant in ButtonM3EStyle.values)
ButtonM3E(
label: Text(variant.name),
style: variant,
size: size,
shape: shape,
onPressed: () {},
),
],
),
],
],
],
),
);
}
}

View file

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'section_card.dart';
class FabSection extends StatelessWidget {
const FabSection({super.key});
@override
Widget build(BuildContext context) {
void onPressed() {
// Placeholder function onPressed
}
return SectionCard(
title: 'FabM3E',
subtitle:
'Generated from enums: kind × size. Includes Extended FAB examples.',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 12,
runSpacing: 12,
children: [
for (final kind in FabM3EKind.values)
for (final size in FabM3ESize.values)
FabM3E(
icon: const Icon(Icons.add),
kind: kind,
size: size,
onPressed: onPressed),
],
),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
ExtendedFabM3E(
label: Text('Create'),
icon: Icon(Icons.add),
onPressed: onPressed),
ExtendedFabM3E(
label: Text('Edit'),
icon: Icon(Icons.edit),
kind: FabM3EKind.secondary,
onPressed: onPressed),
ExtendedFabM3E(
label: Text('Share'),
icon: Icon(Icons.share),
kind: FabM3EKind.tertiary,
onPressed: onPressed),
],
),
],
),
);
}
}

View file

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'section_card.dart';
class IconButtonSection extends StatelessWidget {
const IconButtonSection({super.key});
@override
Widget build(BuildContext context) {
return SectionCard(
title: 'IconButtonM3E',
subtitle: 'Generated from enums: variant × size (round shape, default width).',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final variant in IconButtonM3EVariant.values) ...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(variant.name, style: Theme.of(context).textTheme.titleMedium),
),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
for (final size in IconButtonM3ESize.values)
IconButtonM3E(
icon: const Icon(Icons.favorite),
variant: variant,
size: size,
onPressed: () {},
),
],
),
],
],
),
);
}
}

View file

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'section_card.dart';
class LoadingIndicatorSection extends StatelessWidget {
const LoadingIndicatorSection({super.key});
@override
Widget build(BuildContext context) {
return SectionCard(
title: 'LoadingIndicatorM3E',
subtitle: 'Generated from enums: variant values.',
child: Wrap(
spacing: 16,
runSpacing: 16,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
for (final v in LoadingIndicatorM3EVariant.values)
LoadingIndicatorM3E(variant: v),
],
),
);
}
}

View file

@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'section_card.dart';
class NavigationSection extends StatefulWidget {
const NavigationSection({super.key});
@override
State<NavigationSection> createState() => _NavigationSectionState();
}
class _NavigationSectionState extends State<NavigationSection> {
int _barIndex = 0;
int _railIndex = 0;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final m3e = theme.extension<M3ETheme>() ?? M3ETheme.defaults(theme.colorScheme);
return SectionCard(
title: 'Navigation',
subtitle: 'Generated from enums: NavBar indicator styles and Rail indicator styles.',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text('Navigation Bar', style: theme.textTheme.titleMedium),
),
Wrap(
runSpacing: 12,
children: [
for (final style in NavBarM3EIndicatorStyle.values)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text('indicator: ${style.name}', style: theme.textTheme.labelLarge),
),
NavigationBarM3E(
selectedIndex: _barIndex,
onDestinationSelected: (i) => setState(() => _barIndex = i),
indicatorStyle: style,
destinations: const [
NavigationDestinationM3E(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: 'Home'),
NavigationDestinationM3E(icon: Icon(Icons.search_outlined), selectedIcon: Icon(Icons.search), label: 'Search', badgeDot: true),
NavigationDestinationM3E(icon: Icon(Icons.favorite_outline), selectedIcon: Icon(Icons.favorite), label: 'Favorites', badgeCount: 2),
NavigationDestinationM3E(icon: Icon(Icons.person_outline), selectedIcon: Icon(Icons.person), label: 'Profile'),
],
),
const SizedBox(height: 8),
],
),
],
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text('Navigation Rail', style: theme.textTheme.titleMedium),
),
Container(
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHighest,
borderRadius: m3e.shapes.round.lg,
),
height: 220,
child: Row(
children: [
for (final style in RailIndicatorStyle.values) ...[
NavigationRailM3E(
selectedIndex: _railIndex,
onDestinationSelected: (i) => setState(() => _railIndex = i),
indicatorStyle: style,
destinations: const [
RailDestinationM3E(icon: Icon(Icons.dashboard_outlined), selectedIcon: Icon(Icons.dashboard), label: 'Dash'),
RailDestinationM3E(icon: Icon(Icons.analytics_outlined), selectedIcon: Icon(Icons.analytics), label: 'Reports'),
RailDestinationM3E(icon: Icon(Icons.settings_outlined), selectedIcon: Icon(Icons.settings), label: 'Settings'),
],
),
const VerticalDivider(width: 1),
],
Expanded(
child: Center(child: Text('Selected: $_railIndex')),
),
],
),
),
],
),
);
}
}

View file

@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'section_card.dart';
class ProgressSection extends StatelessWidget {
const ProgressSection({super.key});
@override
Widget build(BuildContext context) {
return SectionCard(
title: 'ProgressIndicatorM3E',
subtitle: 'Generated from enums: circular sizes and linear variants.',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text('Circular - Wavy',
style: Theme.of(context).textTheme.titleMedium),
),
Wrap(
spacing: 16,
runSpacing: 16,
children: [
for (final s in ProgressM3ESize.values) ...[
CircularProgressM3E(
size: s,
value: 0.4,
),
CircularProgressM3E(
size: s,
value: 0.6,
showCenterLabel: s != ProgressM3ESize.small),
],
],
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text('Circular - Flat',
style: Theme.of(context).textTheme.titleMedium),
),
Wrap(
spacing: 16,
runSpacing: 16,
children: [
for (final s in ProgressM3ESize.values) ...[
CircularProgressM3E(
size: s,
value: 0.4,
shape: CircularBarShapeM3E.flat,
),
CircularProgressM3E(
size: s,
shape: CircularBarShapeM3E.flat,
value: 0.6,
showCenterLabel: s != ProgressM3ESize.small),
],
],
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text('Linear - Wavy',
style: Theme.of(context).textTheme.titleMedium),
),
Wrap(
spacing: 16,
runSpacing: 16,
children: [
for (final v in LinearProgressM3EVariant.values)
LinearProgressM3E(
minWidth: 220,
variant: v,
value: v == LinearProgressM3EVariant.determinate ? 0.6 : null,
bufferValue:
v == LinearProgressM3EVariant.buffer ? 0.8 : null,
),
],
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text('Linear - Flat',
style: Theme.of(context).textTheme.titleMedium),
),
Wrap(
spacing: 16,
runSpacing: 16,
children: [
for (final v in LinearProgressM3EVariant.values)
LinearProgressM3E(
minWidth: 220,
variant: v,
shape: LinearBarShapeM3E.flat,
value: v == LinearProgressM3EVariant.determinate ? 0.6 : null,
bufferValue:
v == LinearProgressM3EVariant.buffer ? 0.8 : null,
),
],
),
],
),
);
}
}

View file

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
class SectionCard extends StatelessWidget {
const SectionCard(
{super.key, required this.title, this.subtitle, required this.child});
final String title;
final String? subtitle;
final Widget child;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final m3e =
theme.extension<M3ETheme>() ?? M3ETheme.defaults(theme.colorScheme);
return Padding(
padding: const EdgeInsets.only(bottom: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(title, style: theme.textTheme.titleLarge),
subtitle: subtitle == null ? null : Text(subtitle!),
),
Material(
color: theme.colorScheme.surfaceContainerLow,
borderRadius: m3e.shapes.round.lg,
child: Padding(
padding: const EdgeInsets.all(16),
child: child,
),
),
],
),
);
}
}

View file

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'section_card.dart';
class SliderSection extends StatefulWidget {
const SliderSection({super.key});
@override
State<SliderSection> createState() => _SliderSectionState();
}
class _SliderSectionState extends State<SliderSection> {
double _value = 40;
RangeValues _range = const RangeValues(25, 75);
@override
Widget build(BuildContext context) {
return SectionCard(
title: 'SliderM3E',
subtitle: 'Generated from enums: size × emphasis (round shape).',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final size in SliderM3ESize.values) ...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text('size: ${size.name}', style: Theme.of(context).textTheme.titleMedium),
),
Wrap(
runSpacing: 12,
children: [
for (final emp in SliderM3EEmphasis.values)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('emphasis: ${emp.name}', style: Theme.of(context).textTheme.labelLarge),
SliderM3E(
value: _value,
onChanged: (v) => setState(() => _value = v),
min: 0,
max: 100,
label: _value.toStringAsFixed(0),
size: size,
emphasis: emp,
startIcon: const Icon(Icons.volume_mute),
endIcon: const Icon(Icons.volume_up),
),
RangeSliderM3E(
values: _range,
onChanged: (v) => setState(() => _range = v),
min: 0,
max: 100,
size: size,
emphasis: emp,
labels: RangeLabels(
_range.start.toStringAsFixed(0),
_range.end.toStringAsFixed(0),
),
),
],
),
),
],
),
],
],
),
);
}
}

View file

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'section_card.dart';
class SplitButtonSection extends StatelessWidget {
const SplitButtonSection({super.key});
@override
Widget build(BuildContext context) {
final items = const [
SplitButtonM3EItem<String>(value: 'one', child: 'One'),
SplitButtonM3EItem<String>(value: 'two', child: 'Two'),
SplitButtonM3EItem<String>(value: 'three', child: 'Three'),
];
return SectionCard(
title: 'SplitButtonM3E',
subtitle: 'Generated from enums: emphasis × size (round shape).',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final emphasis in SplitButtonM3EEmphasis.values) ...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(emphasis.name, style: Theme.of(context).textTheme.titleMedium),
),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
for (final size in SplitButtonM3ESize.values)
SplitButtonM3E<String>(
label: emphasis.name,
size: size,
emphasis: emphasis,
onPressed: () {},
items: items,
),
],
),
],
],
),
);
}
}

View file

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:m3e_collection/m3e_collection.dart';
import 'section_card.dart';
class ToolbarSection extends StatelessWidget {
const ToolbarSection({super.key});
@override
Widget build(BuildContext context) {
final actions = [
ToolbarActionM3E(icon: Icons.search, onPressed: () {}),
ToolbarActionM3E(icon: Icons.share, onPressed: () {}),
ToolbarActionM3E(icon: Icons.delete, onPressed: () {}, isDestructive: true, label: 'Delete'),
ToolbarActionM3E(icon: Icons.settings, onPressed: () {}, label: 'Settings'),
];
return SectionCard(
title: 'ToolbarM3E',
subtitle: 'Generated from enums: variant × size (round shape).',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final variant in ToolbarM3EVariant.values) ...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(variant.name, style: Theme.of(context).textTheme.titleMedium),
),
Wrap(
runSpacing: 12,
children: [
for (final size in ToolbarM3ESize.values)
ToolbarM3E(
titleText: 'Toolbar',
subtitleText: 'size: ${size.name}',
actions: actions,
maxInlineActions: 2,
variant: variant,
size: size,
),
],
),
],
],
),
);
}
}