material_design 0.24.0-dev copy "material_design: ^0.24.0-dev" to clipboard
material_design: ^0.24.0-dev copied to clipboard

The fastest path to consistent Material Design UIs in Flutter. Build beautiful apps aligned with official metrics and guidelines using a powerful set of ready-to-use design tokens and helper widgets.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:material_design/material_design.dart';

/// Entry point of the Material Design 3 Demo application.
///
/// This demo showcases the comprehensive Material Design 3 token system,
/// including foundations, styles, and components with proper responsive
/// design patterns and adaptive theming capabilities.
void main() {
  runApp(const MaterialDesignDemo());
}

/// Root application widget that demonstrates Material Design 3 capabilities.
///
/// Features:
/// - Dynamic color theming with seed color customization
/// - Light/dark mode switching with proper M3 color schemes
/// - Responsive layout patterns following M3 breakpoint guidelines
/// - Comprehensive showcase of M3 tokens, components, and patterns
class MaterialDesignDemo extends StatefulWidget {
  const MaterialDesignDemo({super.key});

  @override
  State<MaterialDesignDemo> createState() => _MaterialDesignDemoState();
}

class _MaterialDesignDemoState extends State<MaterialDesignDemo>
    with TickerProviderStateMixin {
  /// Current theme mode (light/dark)
  ThemeMode _themeMode = ThemeMode.light;

  /// Seed color for dynamic color generation
  Color _seedColor = Colors.blue;

  /// Animation controller for theme transitions
  late AnimationController _themeAnimationController;

  @override
  void initState() {
    super.initState();
    _themeAnimationController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
  }

  @override
  void dispose() {
    _themeAnimationController.dispose();
    super.dispose();
  }

  /// Toggles between light and dark theme with smooth animation
  void _toggleTheme() {
    setState(() {
      _themeMode =
          _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
    });
    _themeAnimationController.forward().then((_) {
      _themeAnimationController.reverse();
    });
  }

  /// Changes the seed color for dynamic theming
  void _changeSeedColor(Color color) {
    setState(() {
      _seedColor = color;
    });
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _themeAnimationController,
      builder: (context, child) {
        return MaterialApp(
          title: 'Material Design 3 Demo',
          theme: _buildLightTheme(),
          darkTheme: _buildDarkTheme(),
          themeMode: _themeMode,
          debugShowCheckedModeBanner: false,
          home: DemoHomePage(
            onThemeToggle: _toggleTheme,
            onSeedColorChange: _changeSeedColor,
            currentSeedColor: _seedColor,
            isDark: _themeMode == ThemeMode.dark,
            themeAnimationValue: _themeAnimationController.value,
          ),
        );
      },
    );
  }

  /// Builds the light theme with M3 specifications
  ThemeData _buildLightTheme() {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: _seedColor,
      brightness: Brightness.light,
    );

    return ThemeData.from(
      colorScheme: colorScheme,
      useMaterial3: true,
    ).copyWith(
      // Enhanced M3 component themes
      appBarTheme: AppBarTheme(
        backgroundColor: colorScheme.surfaceContainer,
        foregroundColor: colorScheme.onSurface,
        elevation: 0,
        scrolledUnderElevation: 1,
      ),
      cardTheme: CardThemeData(
        elevation: 1,
        shape: RoundedRectangleBorder(
          borderRadius: M3BorderRadius.medium,
        ),
      ),
      filledButtonTheme: FilledButtonThemeData(
        style: FilledButton.styleFrom(
          shape: RoundedRectangleBorder(
            borderRadius: M3BorderRadius.full,
          ),
        ),
      ),
    );
  }

  /// Builds the dark theme with M3 specifications
  ThemeData _buildDarkTheme() {
    final colorScheme = ColorScheme.fromSeed(
      seedColor: _seedColor,
      brightness: Brightness.dark,
    );

    return ThemeData.from(
      colorScheme: colorScheme,
      useMaterial3: true,
    ).copyWith(
      // Enhanced M3 component themes for dark mode
      appBarTheme: AppBarTheme(
        backgroundColor: colorScheme.surfaceContainer,
        foregroundColor: colorScheme.onSurface,
        elevation: 0,
        scrolledUnderElevation: 1,
      ),
      cardTheme: CardThemeData(
        elevation: 1,
        shape: RoundedRectangleBorder(
          borderRadius: M3BorderRadius.medium,
        ),
      ),
      filledButtonTheme: FilledButtonThemeData(
        style: FilledButton.styleFrom(
          shape: RoundedRectangleBorder(
            borderRadius: M3BorderRadius.full,
          ),
        ),
      ),
    );
  }
}

/// Main demo page showcasing Material Design 3 tokens and components.
///
/// Implements responsive design patterns following M3 breakpoint guidelines:
/// - Compact: < 600dp (mobile portrait)
/// - Medium: 600-839dp (mobile landscape, small tablet)
/// - Expanded: 840-1199dp (tablet, small desktop)
/// - Large: 1200-1599dp (desktop)
/// - Extra Large: ≥ 1600dp (large desktop)
class DemoHomePage extends StatefulWidget {
  final VoidCallback onThemeToggle;
  final Function(Color) onSeedColorChange;
  final Color currentSeedColor;
  final bool isDark;
  final double themeAnimationValue;

  const DemoHomePage({
    super.key,
    required this.onThemeToggle,
    required this.onSeedColorChange,
    required this.currentSeedColor,
    required this.isDark,
    required this.themeAnimationValue,
  });

  @override
  State<DemoHomePage> createState() => _DemoHomePageState();
}

class _DemoHomePageState extends State<DemoHomePage>
    with TickerProviderStateMixin {
  /// Current elevation value for demonstration
  double _elevation = 1.0;

  /// Current visual density setting
  VisualDensity _visualDensity = M3VisualDensity.standard;

  /// Animation controller for page transitions
  late AnimationController _pageAnimationController;

  /// Animation controller for component demonstrations
  late AnimationController _componentAnimationController;

  /// Current selected navigation index
  int _selectedIndex = 0;

  @override
  void initState() {
    super.initState();
    _pageAnimationController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
    _componentAnimationController = AnimationController(
      duration: const Duration(milliseconds: 200),
      vsync: this,
    );

    // Start with a subtle entrance animation
    _pageAnimationController.forward();
  }

  @override
  void dispose() {
    _pageAnimationController.dispose();
    _componentAnimationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    final screenSize = MediaQuery.of(context).size;
    final breakpoint = _getCurrentBreakpoint(screenSize.width);

    return AnimatedBuilder(
      animation: _pageAnimationController,
      builder: (context, child) {
        return Scaffold(
          appBar: _buildAppBar(context, colorScheme),
          body: _buildResponsiveBody(context, breakpoint),
          floatingActionButton: _buildFloatingActionButton(context),
          bottomNavigationBar: breakpoint == M3ScreenSize.compact
              ? _buildBottomNavigationBar(context)
              : null,
        );
      },
    );
  }

  /// Determines the current M3 breakpoint based on screen width
  M3ScreenSize _getCurrentBreakpoint(double width) {
    if (width < M3Breakpoints.medium) return M3ScreenSize.compact;
    if (width < M3Breakpoints.expanded) return M3ScreenSize.medium;
    if (width < M3Breakpoints.large) return M3ScreenSize.expanded;
    if (width < M3Breakpoints.extraLarge) return M3ScreenSize.large;
    return M3ScreenSize.extraLarge;
  }

  /// Builds the responsive app bar with M3 specifications
  PreferredSizeWidget _buildAppBar(
      BuildContext context, ColorScheme colorScheme) {
    return AppBar(
      title: AnimatedOpacity(
        opacity: 1.0 - (widget.themeAnimationValue * 0.3),
        duration: const Duration(milliseconds: 300),
        child: const Text('Material Design 3 Demo'),
      ),
      actions: [
        AnimatedRotation(
          turns: widget.themeAnimationValue * 0.5,
          duration: const Duration(milliseconds: 300),
          child: IconButton(
            icon: Icon(widget.isDark ? Icons.light_mode : Icons.dark_mode),
            onPressed: widget.onThemeToggle,
            tooltip: 'Toggle theme mode',
          ),
        ),
        _ColorPickerButton(
          currentColor: widget.currentSeedColor,
          onColorChanged: widget.onSeedColorChange,
        ),
        const SizedBox(height: M3Spacings.space8),
      ],
    );
  }

  /// Builds the responsive body layout based on current breakpoint
  Widget _buildResponsiveBody(BuildContext context, M3ScreenSize breakpoint) {
    switch (breakpoint) {
      case M3ScreenSize.compact:
        return _buildCompactLayout(context);
      case M3ScreenSize.medium:
      case M3ScreenSize.expanded:
        return _buildMediumLayout(context);
      case M3ScreenSize.large:
      case M3ScreenSize.extraLarge:
        return _buildLargeLayout(context);
    }
  }

  /// Compact layout for mobile devices
  Widget _buildCompactLayout(BuildContext context) {
    return _buildMainContent(context, padding: M3Spacings.space16);
  }

  /// Medium layout for tablets and small desktops
  Widget _buildMediumLayout(BuildContext context) {
    return Row(
      children: [
        _buildNavigationRail(context),
        const VerticalDivider(thickness: 1, width: 1),
        Expanded(
          child: _buildMainContent(context, padding: M3Spacings.space24),
        ),
      ],
    );
  }

  /// Large layout for desktop devices
  Widget _buildLargeLayout(BuildContext context) {
    return Row(
      children: [
        _buildNavigationRail(context),
        const VerticalDivider(thickness: 1, width: 1),
        Expanded(
          flex: 2,
          child: _buildMainContent(context, padding: M3Spacings.space32),
        ),
        const VerticalDivider(thickness: 1, width: 1),
        Expanded(
          child: _buildSidePanel(context),
        ),
      ],
    );
  }

  /// Builds the navigation rail for larger screens
  Widget _buildNavigationRail(BuildContext context) {
    return NavigationRail(
      selectedIndex: _selectedIndex,
      onDestinationSelected: (index) {
        setState(() {
          _selectedIndex = index;
        });
      },
      labelType: NavigationRailLabelType.selected,
      destinations: const [
        NavigationRailDestination(
          icon: Icon(Icons.foundation_outlined),
          selectedIcon: Icon(Icons.foundation),
          label: Text('Foundations'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.style_outlined),
          selectedIcon: Icon(Icons.style),
          label: Text('Styles'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.widgets_outlined),
          selectedIcon: Icon(Icons.widgets),
          label: Text('Components'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.tune_outlined),
          selectedIcon: Icon(Icons.tune),
          label: Text('Interactive'),
        ),
      ],
    );
  }

  /// Builds the bottom navigation bar for compact screens
  Widget _buildBottomNavigationBar(BuildContext context) {
    return NavigationBar(
      selectedIndex: _selectedIndex,
      onDestinationSelected: (index) {
        setState(() {
          _selectedIndex = index;
        });
      },
      destinations: const [
        NavigationDestination(
          icon: Icon(Icons.foundation_outlined),
          selectedIcon: Icon(Icons.foundation),
          label: 'Foundations',
        ),
        NavigationDestination(
          icon: Icon(Icons.style_outlined),
          selectedIcon: Icon(Icons.style),
          label: 'Styles',
        ),
        NavigationDestination(
          icon: Icon(Icons.widgets_outlined),
          selectedIcon: Icon(Icons.widgets),
          label: 'Components',
        ),
        NavigationDestination(
          icon: Icon(Icons.tune_outlined),
          selectedIcon: Icon(Icons.tune),
          label: 'Interactive',
        ),
      ],
    );
  }

  /// Builds the main content area
  Widget _buildMainContent(BuildContext context, {required double padding}) {
    return SingleChildScrollView(
      padding: EdgeInsets.all(padding),
      child: FadeTransition(
        opacity: _pageAnimationController,
        child: SlideTransition(
          position: Tween<Offset>(
            begin: const Offset(0, 0.1),
            end: Offset.zero,
          ).animate(CurvedAnimation(
            parent: _pageAnimationController,
            curve: Curves.easeOutCubic,
          )),
          child: _buildSelectedSection(),
        ),
      ),
    );
  }

  /// Builds the selected section based on navigation index
  Widget _buildSelectedSection() {
    switch (_selectedIndex) {
      case 0:
        return _buildFoundationsContent();
      case 1:
        return _buildStylesContent();
      case 2:
        return _buildComponentsContent();
      case 3:
        return _buildInteractiveContent();
      default:
        return _buildFoundationsContent();
    }
  }

  /// Builds the side panel for large screens
  Widget _buildSidePanel(BuildContext context) {
    return SingleChildScrollView(
      padding: EdgeInsets.all(M3Spacings.space16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildInfoCard(context),
          const SizedBox(height: M3Spacings.space16),
          _buildQuickActions(context),
        ],
      ),
    );
  }

  /// Builds the floating action button with M3 styling
  Widget? _buildFloatingActionButton(BuildContext context) {
    return FloatingActionButton.extended(
      onPressed: () {
        _componentAnimationController.forward().then((_) {
          _componentAnimationController.reverse();
        });
      },
      icon: AnimatedRotation(
        turns: _componentAnimationController.value,
        duration: const Duration(milliseconds: 200),
        child: const Icon(Icons.refresh),
      ),
      label: const Text('Refresh Demo'),
    );
  }

  Widget _buildInfoCard(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(M3Spacings.space16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Material Design 3 Library Demo',
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            const SizedBox(height: M3Spacings.space8),
            Text(
              'This demo showcases the key features of the Material Design 3 library, including design tokens, components, and utilities.',
              style: Theme.of(context).textTheme.bodyMedium,
            ),
            const SizedBox(height: M3Spacings.space12),
            Wrap(
              spacing: M3Spacings.space8,
              children: [
                Chip(
                  label: Text(
                      'Screen: ${MediaQuery.of(context).size.width.toInt()}px'),
                  avatar: const Icon(Icons.aspect_ratio, size: 16),
                ),
                Chip(
                  label: Text('Breakpoint: ${_getCurrentBreakpointName()}'),
                  avatar: const Icon(Icons.devices, size: 16),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  String _getCurrentBreakpointName() {
    final width = MediaQuery.of(context).size.width;
    if (width < M3Breakpoints.medium) return 'Compact';
    if (width < M3Breakpoints.expanded) return 'Medium';
    if (width < M3Breakpoints.large) return 'Expanded';
    if (width < M3Breakpoints.extraLarge) return 'Large';
    return 'Extra Large';
  }

  String _getDensityName(VisualDensity density) {
    if (density == M3VisualDensity.standard) return 'Standard';
    if (density == M3VisualDensity.comfortable) return 'Comfortable';
    if (density == M3VisualDensity.compact) return 'Compact';
    if (density == M3VisualDensity.adaptivePlatformDensity) {
      return 'Adaptive';
    }
    return 'Custom';
  }

  Widget _buildSpacingDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Spacing Tokens', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Wrap(
          spacing: M3Spacings.space8,
          runSpacing: M3Spacings.space8,
          children: [
            M3Spacings.space4,
            M3Spacings.space8,
            M3Spacings.space12,
            M3Spacings.space16,
            M3Spacings.space24,
            M3Spacings.space32,
          ].map((token) => _SpacingExample(spacing: token)).toList(),
        ),
      ],
    );
  }

  Widget _buildVisualDensityDemo() {
    // Versão mais limpa usando uma lista mista
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Visual Density', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Wrap(
          spacing: M3Spacings.space8,
          children: _buildDensityChips(),
        ),
        const SizedBox(height: M3Spacings.space8),
        Card(
          color: Theme.of(context).colorScheme.onPrimary,
          child: ListTile(
            title: Text(
                'List item with ${_getDensityName(_visualDensity)} density'),
            subtitle: Text('Density value: $_visualDensity'),
            leading: const Icon(Icons.density_medium),
            visualDensity: _visualDensity,
          ),
        ),
      ],
    );
  }

  List<Widget> _buildDensityChips() {
    final densityOptions = [
      ('Standard', M3VisualDensity.standard),
      ('Comfortable', M3VisualDensity.comfortable),
      ('Compact', M3VisualDensity.compact),
      ('Adaptive', M3VisualDensity.adaptivePlatformDensity),
    ];

    return densityOptions.map((option) {
      final (name, density) = option;
      return FilterChip(
        label: Text(name),
        selected: _visualDensity == density,
        onSelected: (selected) {
          if (selected) {
            setState(() => _visualDensity = density);
          }
        },
      );
    }).toList();
  }

  Widget _buildOpacityDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Opacity Tokens', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Row(
          spacing: M3Spacings.space16,
          children: [
            _OpacityExample(
                stateLayerOpacity: M3StateLayerOpacityToken.hover,
                label: 'Hover'),
            _OpacityExample(
                stateLayerOpacity: M3StateLayerOpacityToken.focus,
                label: 'Focus'),
            _OpacityExample(
                stateLayerOpacity: M3StateLayerOpacityToken.pressed,
                label: 'Pressed'),
            _OpacityExample(
                stateLayerOpacity: M3StateLayerOpacityToken.dragged,
                label: 'Dragged'),
          ],
        ),
      ],
    );
  }

  Widget _buildBorderDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Border Tokens', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Row(
          spacing: M3Spacings.space16,
          children: [
            M3BorderWidthToken.thin,
            M3BorderWidthToken.thick,
            M3BorderWidthToken.extraThick,
          ]
              .map((token) => Expanded(
                    child: Container(
                      padding: EdgeInsets.all(M3Spacings.space12),
                      decoration: BoxDecoration(
                        border: Border.all(
                          width: token.value,
                          color: Theme.of(context).colorScheme.outline,
                        ),
                        borderRadius: M3BorderRadius.small,
                      ),
                      child: Text(
                        '${token.name}\n${token.value}px',
                        textAlign: TextAlign.center,
                        style: Theme.of(context).textTheme.bodySmall,
                      ),
                    ),
                  ))
              .toList(),
        ),
      ],
    );
  }

  Widget _buildColorDemo(ColorScheme colorScheme) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Color Scheme', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Wrap(
          spacing: M3Spacings.space8,
          runSpacing: M3Spacings.space8,
          children: [
            _ColorSwatch(color: colorScheme.primary, label: 'Primary'),
            _ColorSwatch(color: colorScheme.secondary, label: 'Secondary'),
            _ColorSwatch(color: colorScheme.tertiary, label: 'Tertiary'),
            _ColorSwatch(color: colorScheme.error, label: 'Error'),
            _ColorSwatch(color: colorScheme.surface, label: 'Surface'),
            _ColorSwatch(
                color: colorScheme.surfaceContainer,
                label: 'Surface Container'),
          ],
        ),
      ],
    );
  }

  Widget _buildTypographyDemo(TextTheme textTheme) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Typography Scale',
            style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Display Large', style: textTheme.displayLarge),
            Text('Headline Large', style: textTheme.headlineLarge),
            Text('Title Large', style: textTheme.titleLarge),
            Text('Body Large', style: textTheme.bodyLarge),
            Text('Label Large', style: textTheme.labelLarge),
          ],
        ),
      ],
    );
  }

  Widget _buildShapeDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Shape Tokens', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Row(
          spacing: M3Spacings.space16,
          children: [
            M3BorderRadius.zero,
            M3BorderRadius.small,
            M3BorderRadius.medium,
            M3BorderRadius.large,
            M3BorderRadius.extraLarge,
          ]
              .map((token) => Expanded(
                    child: Container(
                      height: 60,
                      decoration: BoxDecoration(
                        color: Theme.of(context).colorScheme.surfaceContainer,
                        borderRadius: token,
                      ),
                      child: Center(
                        child: Text(
                          token.toString(),
                          style: Theme.of(context).textTheme.bodySmall,
                          textAlign: TextAlign.center,
                        ),
                      ),
                    ),
                  ))
              .toList(),
        ),
      ],
    );
  }

  Widget _buildElevationDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Elevation', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Text('Current elevation: ${_elevation.toInt()}'),
        Slider(
          value: _elevation,
          min: 0,
          max: 5,
          divisions: 5,
          onChanged: (value) => setState(() => _elevation = value),
        ),
        const SizedBox(height: M3Spacings.space8),
        Card(
          elevation: _elevation,
          child: Padding(
            padding: EdgeInsets.all(M3Spacings.space16),
            child: Text('Card with elevation ${_elevation.toInt()}'),
          ),
        ),
      ],
    );
  }

  Widget _buildButtonsDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Buttons', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Wrap(
          spacing: M3Spacings.space8,
          runSpacing: M3Spacings.space8,
          children: [
            ElevatedButton(
              onPressed: () {},
              child: const Text('Elevated'),
            ),
            FilledButton(
              onPressed: () {},
              child: const Text('Filled'),
            ),
            OutlinedButton(
              onPressed: () {},
              child: const Text('Outlined'),
            ),
            TextButton(
              onPressed: () {},
              child: const Text('Text'),
            ),
            IconButton(
              onPressed: () {},
              icon: const Icon(Icons.favorite),
            ),
            FloatingActionButton(
              onPressed: () {},
              child: const Icon(Icons.add),
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildCardsDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Cards', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Row(
          children: [
            Expanded(
              child: Card(
                child: Padding(
                  padding: EdgeInsets.all(M3Spacings.space16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('Elevated Card',
                          style: Theme.of(context).textTheme.titleSmall),
                      const SizedBox(height: M3Spacings.space8),
                      Text('Default Material 3 card with elevation',
                          style: Theme.of(context).textTheme.bodySmall),
                    ],
                  ),
                ),
              ),
            ),
            const SizedBox(width: M3Spacings.space8),
            Expanded(
              child: Card.outlined(
                child: Padding(
                  padding: EdgeInsets.all(M3Spacings.space16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('Outlined Card',
                          style: Theme.of(context).textTheme.titleSmall),
                      const SizedBox(height: M3Spacings.space8),
                      Text('Card with outline border',
                          style: Theme.of(context).textTheme.bodySmall),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildChipsDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Chips', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Wrap(
          spacing: M3Spacings.space8,
          runSpacing: M3Spacings.space8,
          children: [
            const Chip(
              label: Text('Assist Chip'),
              avatar: Icon(Icons.star, size: 16),
            ),
            FilterChip(
              label: const Text('Filter Chip'),
              selected: true,
              onSelected: (value) {},
            ),
            InputChip(
              label: const Text('Input Chip'),
              onDeleted: () {},
            ),
            ActionChip(
              label: const Text('Action Chip'),
              onPressed: () {},
              avatar: const Icon(Icons.settings, size: 16),
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildNavigationDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Navigation', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        const Text('Navigation components adapt based on screen size:'),
        const SizedBox(height: M3Spacings.space8),
        Wrap(
          spacing: M3Spacings.space8,
          runSpacing: M3Spacings.space8,
          children: [
            const Chip(
              label: Text('NavigationRail (≥ Medium)'),
              avatar: Icon(Icons.view_sidebar, size: 16),
            ),
            const Chip(
              label: Text('NavigationBar (Compact)'),
              avatar: Icon(Icons.navigation, size: 16),
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildQuickActions(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(M3Spacings.space16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Quick Actions',
                style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: M3Spacings.space12),
            FilledButton.icon(
              onPressed: widget.onThemeToggle,
              icon: Icon(widget.isDark ? Icons.light_mode : Icons.dark_mode),
              label: Text(widget.isDark ? 'Light Mode' : 'Dark Mode'),
            ),
            const SizedBox(height: M3Spacings.space8),
            OutlinedButton.icon(
              onPressed: () {
                _componentAnimationController.forward().then((_) {
                  _componentAnimationController.reverse();
                });
              },
              icon: const Icon(Icons.refresh),
              label: const Text('Animate'),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildThemeControls() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Theme Controls', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        SwitchListTile(
          title: const Text('Dark Mode'),
          subtitle: const Text('Toggle between light and dark themes'),
          value: widget.isDark,
          onChanged: (value) => widget.onThemeToggle(),
        ),
      ],
    );
  }

  Widget _buildSeedColorPicker(ColorScheme colorScheme) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Seed Colors', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        Wrap(
          spacing: M3Spacings.space8,
          children: [
            Colors.blue,
            Colors.red,
            Colors.green,
            Colors.purple,
            Colors.orange,
            Colors.teal,
          ]
              .map((color) => _AnimatedColorSwatch(
                    color: color,
                    isSelected: widget.currentSeedColor == color,
                    onTap: () => widget.onSeedColorChange(color),
                    outlineColor: colorScheme.outline,
                  ))
              .toList(),
        ),
      ],
    );
  }

  Widget _buildAnimationDemo() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Animations', style: Theme.of(context).textTheme.titleMedium),
        const SizedBox(height: M3Spacings.space8),
        AnimatedBuilder(
          animation: _componentAnimationController,
          builder: (context, child) {
            return Transform.scale(
              scale: 1.0 + (_componentAnimationController.value * 0.1),
              child: Card(
                child: Padding(
                  padding: EdgeInsets.all(M3Spacings.space16),
                  child: Row(
                    children: [
                      Icon(
                        Icons.animation,
                        color: Theme.of(context).colorScheme.primary,
                      ),
                      const SizedBox(height: M3Spacings.space8),
                      const Text('Animated component demo'),
                    ],
                  ),
                ),
              ),
            );
          },
        ),
      ],
    );
  }

  /// Builds the foundations content section
  Widget _buildFoundationsContent() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _buildInfoCard(context),
        const SizedBox(height: M3Spacings.space24),
        _SectionCard(
          title: 'Foundations',
          subtitle: 'Design tokens and system foundations',
          children: [
            _buildSpacingDemo(),
            const SizedBox(height: M3Spacings.space16),
            _buildVisualDensityDemo(),
            const SizedBox(height: M3Spacings.space16),
            _buildOpacityDemo(),
            const SizedBox(height: M3Spacings.space16),
            _buildBorderDemo(),
          ],
        ),
      ],
    );
  }

  /// Builds the styles content section
  Widget _buildStylesContent() {
    final colorScheme = Theme.of(context).colorScheme;
    final textTheme = Theme.of(context).textTheme;

    return _SectionCard(
      title: 'Styles',
      subtitle: 'Colors, typography, shapes, and elevation',
      children: [
        _buildColorDemo(colorScheme),
        const SizedBox(height: M3Spacings.space16),
        _buildTypographyDemo(textTheme),
        const SizedBox(height: M3Spacings.space16),
        _buildShapeDemo(),
        const SizedBox(height: M3Spacings.space16),
        _buildElevationDemo(),
      ],
    );
  }

  /// Builds the components content section
  Widget _buildComponentsContent() {
    return _SectionCard(
      title: 'Components',
      subtitle: 'Material Design 3 components showcase',
      children: [
        _buildButtonsDemo(),
        const SizedBox(height: M3Spacings.space16),
        _buildCardsDemo(),
        const SizedBox(height: M3Spacings.space16),
        _buildChipsDemo(),
        const SizedBox(height: M3Spacings.space16),
        _buildNavigationDemo(),
      ],
    );
  }

  /// Builds the interactive content section
  Widget _buildInteractiveContent() {
    final colorScheme = Theme.of(context).colorScheme;

    return _SectionCard(
      title: 'Interactive Demo',
      subtitle: 'Test theme switching and adaptive behavior',
      children: [
        _buildThemeControls(),
        const SizedBox(height: M3Spacings.space16),
        _buildSeedColorPicker(colorScheme),
        const SizedBox(height: M3Spacings.space16),
        _buildAnimationDemo(),
      ],
    );
  }
}

class _SectionCard extends StatelessWidget {
  final String title;
  final String subtitle;
  final List<Widget> children;

  const _SectionCard({
    required this.title,
    required this.subtitle,
    required this.children,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(M3Spacings.space16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: Theme.of(context).textTheme.headlineSmall),
            Text(subtitle, style: Theme.of(context).textTheme.bodyMedium),
            const SizedBox(height: M3Spacings.space16),
            ...children,
          ],
        ),
      ),
    );
  }
}

class _AnimatedColorSwatch extends StatefulWidget {
  final Color color;
  final bool isSelected;
  final VoidCallback onTap;
  final Color outlineColor;

  const _AnimatedColorSwatch({
    required this.color,
    required this.isSelected,
    required this.onTap,
    required this.outlineColor,
  });

  @override
  State<_AnimatedColorSwatch> createState() => _AnimatedColorSwatchState();
}

class _AnimatedColorSwatchState extends State<_AnimatedColorSwatch>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _scaleAnimation;
  bool _isHovered = false;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 150),
      vsync: this,
    );
    _scaleAnimation = Tween<double>(
      begin: 1.0,
      end: 1.1,
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeInOut,
    ));
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  void _handleHover(bool isHovered) {
    setState(() {
      _isHovered = isHovered;
    });

    if (isHovered) {
      _animationController.forward();
    } else {
      _animationController.reverse();
    }
  }

  @override
  Widget build(BuildContext context) {
    return MouseRegion(
      onEnter: (_) => _handleHover(true),
      onExit: (_) => _handleHover(false),
      child: GestureDetector(
        onTap: widget.onTap,
        child: AnimatedBuilder(
          animation: _scaleAnimation,
          builder: (context, child) {
            return Transform.scale(
              scale: _scaleAnimation.value,
              child: AnimatedContainer(
                duration: const Duration(milliseconds: 200),
                width: 40,
                height: 40,
                decoration: BoxDecoration(
                  color: widget.color,
                  shape: BoxShape.circle,
                  border: widget.isSelected
                      ? Border.all(color: widget.outlineColor, width: 3)
                      : _isHovered
                          ? Border.all(color: widget.outlineColor, width: 1)
                          : null,
                  boxShadow: _isHovered || widget.isSelected
                      ? [
                          BoxShadow(
                            color: widget.color.withValues(alpha: 0.3),
                            blurRadius: 8,
                            spreadRadius: 2,
                          ),
                        ]
                      : null,
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

class _ColorSwatch extends StatelessWidget {
  final Color color;
  final String label;

  const _ColorSwatch({required this.color, required this.label});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          width: 60,
          height: 40,
          decoration: BoxDecoration(
            color: color,
            borderRadius: M3BorderRadius.small,
            border: Border.all(
              color:
                  Theme.of(context).colorScheme.outline.withValues(alpha: 0.2),
            ),
          ),
        ),
        const SizedBox(height: M3Spacings.space4),
        Text(
          label,
          textAlign: TextAlign.center,
          style: Theme.of(context).textTheme.bodySmall,
        ),
      ],
    );
  }
}

class _SpacingExample extends StatelessWidget {
  final double spacing;

  const _SpacingExample({required this.spacing});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          width: spacing,
          height: 20,
          decoration: BoxDecoration(
            color: Theme.of(context).colorScheme.primary,
            borderRadius: BorderRadius.circular(2),
          ),
        ),
        const SizedBox(height: M3Spacings.space4),
        Text(
          '${spacing}px',
          textAlign: TextAlign.center,
          style: Theme.of(context).textTheme.bodySmall,
        ),
      ],
    );
  }
}

class _OpacityExample extends StatelessWidget {
  final M3StateLayerOpacityToken stateLayerOpacity;
  final String label;

  const _OpacityExample({required this.stateLayerOpacity, required this.label});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          width: 60,
          height: 40,
          decoration: M3BoxDecoration(
            color: Theme.of(context)
                .colorScheme
                .primary
                .withValues(alpha: stateLayerOpacity.value),
            borderRadius: M3BorderRadius.small,
          ),
        ),
        const SizedBox(height: M3Spacings.space4),
        Text(
          '$label\n${(stateLayerOpacity.value * 100).toInt()}%',
          textAlign: TextAlign.center,
          style: Theme.of(context).textTheme.bodySmall,
        ),
      ],
    );
  }
}

class _ColorPickerButton extends StatelessWidget {
  final Color currentColor;
  final Function(Color) onColorChanged;

  const _ColorPickerButton({
    required this.currentColor,
    required this.onColorChanged,
  });

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<Color>(
      icon: Icon(Icons.color_lens, color: currentColor),
      tooltip: 'Change seed color',
      onSelected: onColorChanged,
      itemBuilder: (context) => [
        Colors.blue,
        Colors.red,
        Colors.green,
        Colors.purple,
        Colors.orange,
        Colors.teal,
        Colors.pink,
        Colors.indigo,
      ]
          .map((color) => PopupMenuItem(
                value: color,
                child: Row(
                  children: [
                    Container(
                      width: 24,
                      height: 24,
                      decoration: BoxDecoration(
                        color: color,
                        shape: BoxShape.circle,
                        border: Border.all(
                          color: Theme.of(context).colorScheme.outlineVariant,
                          width: M3BorderWidthToken.thin.value,
                        ),
                      ),
                    ),
                    const SizedBox(height: M3Spacings.space12),
                    Text(
                      '#${color.toARGB32().toRadixString(16).padLeft(8, '0').substring(2).toUpperCase()}',
                    ),
                  ],
                ),
              ))
          .toList(),
    );
  }
}
23
likes
0
points
787
downloads

Publisher

unverified uploader

Weekly Downloads

The fastest path to consistent Material Design UIs in Flutter. Build beautiful apps aligned with official metrics and guidelines using a powerful set of ready-to-use design tokens and helper widgets.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, meta, vector_math

More

Packages that depend on material_design