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

@ -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,
),
],
),
],
],
),
);
}
}