From a5b9d6de463acf2379f07e23f6a80d525c0416a4 Mon Sep 17 00:00:00 2001 From: Qun Cheng Date: Thu, 3 Sep 2026 16:46:22 -0700 Subject: [PATCH] Migrate M3 button defaults to new gen_defaults --- .../material_ui/lib/src/elevated_button.dart | 143 +------- .../material_ui/lib/src/filled_button.dart | 285 +--------------- .../elevated_button_defaults_m3.g.dart} | 5 + .../filled_button_defaults_m3.g.dart} | 5 + .../filled_tonal_button_defaults_m3.g.dart} | 5 + .../outlined_button_defaults_m3.g.dart} | 5 + .../generated/text_button_defaults_m3.g.dart} | 5 + .../material_ui/lib/src/outlined_button.dart | 134 +------- packages/material_ui/lib/src/text_button.dart | 124 +------ .../tool/gen_defaults/bin/gen_defaults.dart | 8 +- .../templates/button_template.dart | 317 ++++++++++++++++++ .../gen_defaults/test/gen_defaults_test.dart | 97 +++++- 12 files changed, 451 insertions(+), 682 deletions(-) rename packages/material_ui/{tool/gen_defaults/temporarily_excluded/generated/elevated_button_defaults.g.dart => lib/src/generated/elevated_button_defaults_m3.g.dart} (94%) rename packages/material_ui/{tool/gen_defaults/temporarily_excluded/generated/filled_button_defaults.g.dart => lib/src/generated/filled_button_defaults_m3.g.dart} (95%) rename packages/material_ui/{tool/gen_defaults/temporarily_excluded/generated/filled_tonal_button_defaults.g.dart => lib/src/generated/filled_tonal_button_defaults_m3.g.dart} (95%) rename packages/material_ui/{tool/gen_defaults/temporarily_excluded/generated/outlined_button_defaults.g.dart => lib/src/generated/outlined_button_defaults_m3.g.dart} (94%) rename packages/material_ui/{tool/gen_defaults/temporarily_excluded/generated/text_button_defaults.g.dart => lib/src/generated/text_button_defaults_m3.g.dart} (94%) create mode 100644 packages/material_ui/tool/gen_defaults/templates/button_template.dart diff --git a/packages/material_ui/lib/src/elevated_button.dart b/packages/material_ui/lib/src/elevated_button.dart index ad6d44c9f9c7..27e9895aa07d 100644 --- a/packages/material_ui/lib/src/elevated_button.dart +++ b/packages/material_ui/lib/src/elevated_button.dart @@ -25,6 +25,8 @@ import 'material_state.dart'; import 'theme.dart'; import 'theme_data.dart'; +part 'generated/elevated_button_defaults_m3.g.dart'; + /// A Material Design "elevated button". /// /// Use elevated buttons to add dimension to otherwise mostly flat @@ -507,144 +509,3 @@ class _ElevatedButtonWithIconChild extends StatelessWidget { ); } } - -// BEGIN GENERATED TOKEN PROPERTIES - ElevatedButton - -// Do not edit by hand. The code between the "BEGIN GENERATED" and -// "END GENERATED" comments are generated from data in the Material -// Design token database by the script: -// dev/tools/gen_defaults/bin/gen_defaults.dart. - -// dart format off -class _ElevatedButtonDefaultsM3 extends ButtonStyle { - _ElevatedButtonDefaultsM3(this.context) - : super( - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - ); - - final BuildContext context; - late final ColorScheme _colors = Theme.of(context).colorScheme; - - @override - WidgetStateProperty get textStyle => - MaterialStatePropertyAll(Theme.of(context).textTheme.labelLarge); - - @override - WidgetStateProperty? get backgroundColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.12); - } - return _colors.surfaceContainerLow; - }); - - @override - WidgetStateProperty? get foregroundColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - return _colors.primary; - }); - - @override - WidgetStateProperty? get overlayColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.pressed)) { - return _colors.primary.withOpacity(0.1); - } - if (states.contains(WidgetState.hovered)) { - return _colors.primary.withOpacity(0.08); - } - if (states.contains(WidgetState.focused)) { - return _colors.primary.withOpacity(0.1); - } - return null; - }); - - @override - WidgetStateProperty? get shadowColor => - MaterialStatePropertyAll(_colors.shadow); - - @override - WidgetStateProperty? get surfaceTintColor => - const MaterialStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get elevation => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return 0.0; - } - if (states.contains(WidgetState.pressed)) { - return 1.0; - } - if (states.contains(WidgetState.hovered)) { - return 3.0; - } - if (states.contains(WidgetState.focused)) { - return 1.0; - } - return 1.0; - }); - - @override - WidgetStateProperty? get padding => - MaterialStatePropertyAll(_scaledPadding(context)); - - @override - WidgetStateProperty? get minimumSize => - const MaterialStatePropertyAll(Size(64.0, 40.0)); - - // No default fixedSize - - @override - WidgetStateProperty? get iconSize => - const MaterialStatePropertyAll(18.0); - - @override - WidgetStateProperty? get iconColor { - return WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - if (states.contains(WidgetState.pressed)) { - return _colors.primary; - } - if (states.contains(WidgetState.hovered)) { - return _colors.primary; - } - if (states.contains(WidgetState.focused)) { - return _colors.primary; - } - return _colors.primary; - }); - } - - @override - WidgetStateProperty? get maximumSize => - const MaterialStatePropertyAll(Size.infinite); - - // No default side - - @override - WidgetStateProperty? get shape => - const MaterialStatePropertyAll(StadiumBorder()); - - @override - WidgetStateProperty? get mouseCursor => WidgetStateMouseCursor.adaptiveClickable; - - @override - VisualDensity? get visualDensity => Theme.of(context).visualDensity; - - @override - MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize; - - @override - InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory; -} -// dart format on - -// END GENERATED TOKEN PROPERTIES - ElevatedButton diff --git a/packages/material_ui/lib/src/filled_button.dart b/packages/material_ui/lib/src/filled_button.dart index 3b3a6a64e4cf..477b85936ac6 100644 --- a/packages/material_ui/lib/src/filled_button.dart +++ b/packages/material_ui/lib/src/filled_button.dart @@ -25,6 +25,9 @@ import 'material_state.dart'; import 'theme.dart'; import 'theme_data.dart'; +part 'generated/filled_button_defaults_m3.g.dart'; +part 'generated/filled_tonal_button_defaults_m3.g.dart'; + enum _FilledButtonVariant { filled, tonal } /// A Material Design filled button. @@ -525,285 +528,3 @@ class _FilledButtonWithIconChild extends StatelessWidget { ); } } - -// BEGIN GENERATED TOKEN PROPERTIES - FilledButton - -// Do not edit by hand. The code between the "BEGIN GENERATED" and -// "END GENERATED" comments are generated from data in the Material -// Design token database by the script: -// dev/tools/gen_defaults/bin/gen_defaults.dart. - -// dart format off -class _FilledButtonDefaultsM3 extends ButtonStyle { - _FilledButtonDefaultsM3(this.context) - : super( - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - ); - - final BuildContext context; - late final ColorScheme _colors = Theme.of(context).colorScheme; - - @override - WidgetStateProperty get textStyle => - MaterialStatePropertyAll(Theme.of(context).textTheme.labelLarge); - - @override - WidgetStateProperty? get backgroundColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.12); - } - return _colors.primary; - }); - - @override - WidgetStateProperty? get foregroundColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - return _colors.onPrimary; - }); - - @override - WidgetStateProperty? get overlayColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.pressed)) { - return _colors.onPrimary.withOpacity(0.1); - } - if (states.contains(WidgetState.hovered)) { - return _colors.onPrimary.withOpacity(0.08); - } - if (states.contains(WidgetState.focused)) { - return _colors.onPrimary.withOpacity(0.1); - } - return null; - }); - - @override - WidgetStateProperty? get shadowColor => - MaterialStatePropertyAll(_colors.shadow); - - @override - WidgetStateProperty? get surfaceTintColor => - const MaterialStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get elevation => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return 0.0; - } - if (states.contains(WidgetState.pressed)) { - return 0.0; - } - if (states.contains(WidgetState.hovered)) { - return 1.0; - } - if (states.contains(WidgetState.focused)) { - return 0.0; - } - return 0.0; - }); - - @override - WidgetStateProperty? get padding => - MaterialStatePropertyAll(_scaledPadding(context)); - - @override - WidgetStateProperty? get minimumSize => - const MaterialStatePropertyAll(Size(64.0, 40.0)); - - // No default fixedSize - - @override - WidgetStateProperty? get iconSize => - const MaterialStatePropertyAll(18.0); - - @override - WidgetStateProperty? get iconColor { - return WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - if (states.contains(WidgetState.pressed)) { - return _colors.onPrimary; - } - if (states.contains(WidgetState.hovered)) { - return _colors.onPrimary; - } - if (states.contains(WidgetState.focused)) { - return _colors.onPrimary; - } - return _colors.onPrimary; - }); - } - - @override - WidgetStateProperty? get maximumSize => - const MaterialStatePropertyAll(Size.infinite); - - // No default side - - @override - WidgetStateProperty? get shape => - const MaterialStatePropertyAll(StadiumBorder()); - - @override - WidgetStateProperty? get mouseCursor => WidgetStateMouseCursor.adaptiveClickable; - - @override - VisualDensity? get visualDensity => Theme.of(context).visualDensity; - - @override - MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize; - - @override - InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory; -} -// dart format on - -// END GENERATED TOKEN PROPERTIES - FilledButton - -// BEGIN GENERATED TOKEN PROPERTIES - FilledTonalButton - -// Do not edit by hand. The code between the "BEGIN GENERATED" and -// "END GENERATED" comments are generated from data in the Material -// Design token database by the script: -// dev/tools/gen_defaults/bin/gen_defaults.dart. - -// dart format off -class _FilledTonalButtonDefaultsM3 extends ButtonStyle { - _FilledTonalButtonDefaultsM3(this.context) - : super( - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - ); - - final BuildContext context; - late final ColorScheme _colors = Theme.of(context).colorScheme; - - @override - WidgetStateProperty get textStyle => - MaterialStatePropertyAll(Theme.of(context).textTheme.labelLarge); - - @override - WidgetStateProperty? get backgroundColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.12); - } - return _colors.secondaryContainer; - }); - - @override - WidgetStateProperty? get foregroundColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - return _colors.onSecondaryContainer; - }); - - @override - WidgetStateProperty? get overlayColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.pressed)) { - return _colors.onSecondaryContainer.withOpacity(0.1); - } - if (states.contains(WidgetState.hovered)) { - return _colors.onSecondaryContainer.withOpacity(0.08); - } - if (states.contains(WidgetState.focused)) { - return _colors.onSecondaryContainer.withOpacity(0.1); - } - return null; - }); - - @override - WidgetStateProperty? get shadowColor => - MaterialStatePropertyAll(_colors.shadow); - - @override - WidgetStateProperty? get surfaceTintColor => - const MaterialStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get elevation => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return 0.0; - } - if (states.contains(WidgetState.pressed)) { - return 0.0; - } - if (states.contains(WidgetState.hovered)) { - return 1.0; - } - if (states.contains(WidgetState.focused)) { - return 0.0; - } - return 0.0; - }); - - @override - WidgetStateProperty? get padding => - MaterialStatePropertyAll(_scaledPadding(context)); - - @override - WidgetStateProperty? get minimumSize => - const MaterialStatePropertyAll(Size(64.0, 40.0)); - - // No default fixedSize - - @override - WidgetStateProperty? get iconSize => - const MaterialStatePropertyAll(18.0); - - @override - WidgetStateProperty? get iconColor { - return WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - if (states.contains(WidgetState.pressed)) { - return _colors.onSecondaryContainer; - } - if (states.contains(WidgetState.hovered)) { - return _colors.onSecondaryContainer; - } - if (states.contains(WidgetState.focused)) { - return _colors.onSecondaryContainer; - } - return _colors.onSecondaryContainer; - }); - } - - @override - WidgetStateProperty? get maximumSize => - const MaterialStatePropertyAll(Size.infinite); - - // No default side - - @override - WidgetStateProperty? get shape => - const MaterialStatePropertyAll(StadiumBorder()); - - @override - WidgetStateProperty? get mouseCursor => WidgetStateMouseCursor.adaptiveClickable; - - @override - VisualDensity? get visualDensity => Theme.of(context).visualDensity; - - @override - MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize; - - @override - InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory; -} -// dart format on - -// END GENERATED TOKEN PROPERTIES - FilledTonalButton diff --git a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/elevated_button_defaults.g.dart b/packages/material_ui/lib/src/generated/elevated_button_defaults_m3.g.dart similarity index 94% rename from packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/elevated_button_defaults.g.dart rename to packages/material_ui/lib/src/generated/elevated_button_defaults_m3.g.dart index f771ad323958..f314eb46824d 100644 --- a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/elevated_button_defaults.g.dart +++ b/packages/material_ui/lib/src/generated/elevated_button_defaults_m3.g.dart @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Do not edit by hand. The code is generated from data in the Material +// Design token database by the script: +// packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart. +part of '../elevated_button.dart'; + class _ElevatedButtonDefaultsM3 extends ButtonStyle { _ElevatedButtonDefaultsM3(this.context) : super( diff --git a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/filled_button_defaults.g.dart b/packages/material_ui/lib/src/generated/filled_button_defaults_m3.g.dart similarity index 95% rename from packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/filled_button_defaults.g.dart rename to packages/material_ui/lib/src/generated/filled_button_defaults_m3.g.dart index 4b409a94c55f..5f3738f69b2e 100644 --- a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/filled_button_defaults.g.dart +++ b/packages/material_ui/lib/src/generated/filled_button_defaults_m3.g.dart @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Do not edit by hand. The code is generated from data in the Material +// Design token database by the script: +// packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart. +part of '../filled_button.dart'; + class _FilledButtonDefaultsM3 extends ButtonStyle { _FilledButtonDefaultsM3(this.context) : super( diff --git a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/filled_tonal_button_defaults.g.dart b/packages/material_ui/lib/src/generated/filled_tonal_button_defaults_m3.g.dart similarity index 95% rename from packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/filled_tonal_button_defaults.g.dart rename to packages/material_ui/lib/src/generated/filled_tonal_button_defaults_m3.g.dart index 1e8d87d7d815..ba3ad725fa4c 100644 --- a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/filled_tonal_button_defaults.g.dart +++ b/packages/material_ui/lib/src/generated/filled_tonal_button_defaults_m3.g.dart @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Do not edit by hand. The code is generated from data in the Material +// Design token database by the script: +// packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart. +part of '../filled_button.dart'; + class _FilledTonalButtonDefaultsM3 extends ButtonStyle { _FilledTonalButtonDefaultsM3(this.context) : super( diff --git a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/outlined_button_defaults.g.dart b/packages/material_ui/lib/src/generated/outlined_button_defaults_m3.g.dart similarity index 94% rename from packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/outlined_button_defaults.g.dart rename to packages/material_ui/lib/src/generated/outlined_button_defaults_m3.g.dart index 34fd4eca9bea..cf990e4593eb 100644 --- a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/outlined_button_defaults.g.dart +++ b/packages/material_ui/lib/src/generated/outlined_button_defaults_m3.g.dart @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Do not edit by hand. The code is generated from data in the Material +// Design token database by the script: +// packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart. +part of '../outlined_button.dart'; + class _OutlinedButtonDefaultsM3 extends ButtonStyle { _OutlinedButtonDefaultsM3(this.context) : super( diff --git a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/text_button_defaults.g.dart b/packages/material_ui/lib/src/generated/text_button_defaults_m3.g.dart similarity index 94% rename from packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/text_button_defaults.g.dart rename to packages/material_ui/lib/src/generated/text_button_defaults_m3.g.dart index be07273a9df0..198372d76ced 100644 --- a/packages/material_ui/tool/gen_defaults/temporarily_excluded/generated/text_button_defaults.g.dart +++ b/packages/material_ui/lib/src/generated/text_button_defaults_m3.g.dart @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Do not edit by hand. The code is generated from data in the Material +// Design token database by the script: +// packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart. +part of '../text_button.dart'; + class _TextButtonDefaultsM3 extends ButtonStyle { _TextButtonDefaultsM3(this.context) : super( diff --git a/packages/material_ui/lib/src/outlined_button.dart b/packages/material_ui/lib/src/outlined_button.dart index 78d3a2fe1409..f7d512f6a94b 100644 --- a/packages/material_ui/lib/src/outlined_button.dart +++ b/packages/material_ui/lib/src/outlined_button.dart @@ -25,6 +25,8 @@ import 'outlined_button_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; +part 'generated/outlined_button_defaults_m3.g.dart'; + /// A Material Design "Outlined Button"; essentially a [TextButton] /// with an outlined border. /// @@ -454,135 +456,3 @@ class _OutlinedButtonWithIconChild extends StatelessWidget { ); } } - -// BEGIN GENERATED TOKEN PROPERTIES - OutlinedButton - -// Do not edit by hand. The code between the "BEGIN GENERATED" and -// "END GENERATED" comments are generated from data in the Material -// Design token database by the script: -// dev/tools/gen_defaults/bin/gen_defaults.dart. - -// dart format off -class _OutlinedButtonDefaultsM3 extends ButtonStyle { - _OutlinedButtonDefaultsM3(this.context) - : super( - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - ); - - final BuildContext context; - late final ColorScheme _colors = Theme.of(context).colorScheme; - - @override - WidgetStateProperty get textStyle => - MaterialStatePropertyAll(Theme.of(context).textTheme.labelLarge); - - @override - WidgetStateProperty? get backgroundColor => - const MaterialStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get foregroundColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - return _colors.primary; - }); - - @override - WidgetStateProperty? get overlayColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.pressed)) { - return _colors.primary.withOpacity(0.1); - } - if (states.contains(WidgetState.hovered)) { - return _colors.primary.withOpacity(0.08); - } - if (states.contains(WidgetState.focused)) { - return _colors.primary.withOpacity(0.1); - } - return null; - }); - - @override - WidgetStateProperty? get shadowColor => - const MaterialStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get surfaceTintColor => - const MaterialStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get elevation => - const MaterialStatePropertyAll(0.0); - - @override - WidgetStateProperty? get padding => - MaterialStatePropertyAll(_scaledPadding(context)); - - @override - WidgetStateProperty? get minimumSize => - const MaterialStatePropertyAll(Size(64.0, 40.0)); - - // No default fixedSize - - @override - WidgetStateProperty? get iconSize => - const MaterialStatePropertyAll(18.0); - - @override - WidgetStateProperty? get iconColor { - return WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - if (states.contains(WidgetState.pressed)) { - return _colors.primary; - } - if (states.contains(WidgetState.hovered)) { - return _colors.primary; - } - if (states.contains(WidgetState.focused)) { - return _colors.primary; - } - return _colors.primary; - }); - } - - @override - WidgetStateProperty? get maximumSize => - const MaterialStatePropertyAll(Size.infinite); - - @override - WidgetStateProperty? get side => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return BorderSide(color: _colors.onSurface.withOpacity(0.12)); - } - if (states.contains(WidgetState.focused)) { - return BorderSide(color: _colors.primary); - } - return BorderSide(color: _colors.outline); - }); - - @override - WidgetStateProperty? get shape => - const MaterialStatePropertyAll(StadiumBorder()); - - @override - WidgetStateProperty? get mouseCursor => WidgetStateMouseCursor.adaptiveClickable; - - @override - VisualDensity? get visualDensity => Theme.of(context).visualDensity; - - @override - MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize; - - @override - InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory; -} -// dart format on - -// END GENERATED TOKEN PROPERTIES - OutlinedButton diff --git a/packages/material_ui/lib/src/text_button.dart b/packages/material_ui/lib/src/text_button.dart index 9a76328d0949..f86d0c46c3e6 100644 --- a/packages/material_ui/lib/src/text_button.dart +++ b/packages/material_ui/lib/src/text_button.dart @@ -25,6 +25,8 @@ import 'text_button_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; +part 'generated/text_button_defaults_m3.g.dart'; + /// A Material Design "Text Button". /// /// Use text buttons on toolbars, in dialogs, or inline with other @@ -493,125 +495,3 @@ class _TextButtonWithIconChild extends StatelessWidget { ); } } - -// BEGIN GENERATED TOKEN PROPERTIES - TextButton - -// Do not edit by hand. The code between the "BEGIN GENERATED" and -// "END GENERATED" comments are generated from data in the Material -// Design token database by the script: -// dev/tools/gen_defaults/bin/gen_defaults.dart. - -// dart format off -class _TextButtonDefaultsM3 extends ButtonStyle { - _TextButtonDefaultsM3(this.context) - : super( - animationDuration: kThemeChangeDuration, - enableFeedback: true, - alignment: Alignment.center, - ); - - final BuildContext context; - late final ColorScheme _colors = Theme.of(context).colorScheme; - - @override - WidgetStateProperty get textStyle => - MaterialStatePropertyAll(Theme.of(context).textTheme.labelLarge); - - @override - WidgetStateProperty? get backgroundColor => - const MaterialStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get foregroundColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - return _colors.primary; - }); - - @override - WidgetStateProperty? get overlayColor => - WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.pressed)) { - return _colors.primary.withOpacity(0.1); - } - if (states.contains(WidgetState.hovered)) { - return _colors.primary.withOpacity(0.08); - } - if (states.contains(WidgetState.focused)) { - return _colors.primary.withOpacity(0.1); - } - return null; - }); - - @override - WidgetStateProperty? get shadowColor => - const MaterialStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get surfaceTintColor => - const MaterialStatePropertyAll(Colors.transparent); - - @override - WidgetStateProperty? get elevation => - const MaterialStatePropertyAll(0.0); - - @override - WidgetStateProperty? get padding => - MaterialStatePropertyAll(_scaledPadding(context)); - - @override - WidgetStateProperty? get minimumSize => - const MaterialStatePropertyAll(Size(64.0, 40.0)); - - // No default fixedSize - - @override - WidgetStateProperty? get iconSize => - const MaterialStatePropertyAll(18.0); - - @override - WidgetStateProperty? get iconColor { - return WidgetStateProperty.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return _colors.onSurface.withOpacity(0.38); - } - if (states.contains(WidgetState.pressed)) { - return _colors.primary; - } - if (states.contains(WidgetState.hovered)) { - return _colors.primary; - } - if (states.contains(WidgetState.focused)) { - return _colors.primary; - } - return _colors.primary; - }); - } - - @override - WidgetStateProperty? get maximumSize => - const MaterialStatePropertyAll(Size.infinite); - - // No default side - - @override - WidgetStateProperty? get shape => - const MaterialStatePropertyAll(StadiumBorder()); - - @override - WidgetStateProperty? get mouseCursor => WidgetStateMouseCursor.adaptiveClickable; - - @override - VisualDensity? get visualDensity => Theme.of(context).visualDensity; - - @override - MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize; - - @override - InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory; -} -// dart format on - -// END GENERATED TOKEN PROPERTIES - TextButton diff --git a/packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart b/packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart index e03f5f6de769..551a49ce50c5 100644 --- a/packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart +++ b/packages/material_ui/tool/gen_defaults/bin/gen_defaults.dart @@ -19,8 +19,8 @@ import '../templates/app_bar_template.dart'; // import '../templates/banner_template.dart'; // import '../templates/bottom_app_bar_template.dart'; import '../templates/bottom_sheet_template.dart'; +import '../templates/button_template.dart'; -// import '../templates/button_template.dart'; // import '../templates/card_template.dart'; // import '../templates/checkbox_template.dart'; // import '../templates/chip_template.dart'; @@ -71,7 +71,11 @@ Future main(List args) async { // const BannerTemplateM3().generateFile(verbose: verbose); // const BottomAppBarTemplateM3().generateFile(verbose: verbose); const BottomSheetTemplateM3().generateFile(verbose: verbose); - // const ButtonTemplateM3().generateFile(verbose: verbose); + const ButtonTemplateM3('Elevated Button').generateFile(verbose: verbose); + const ButtonTemplateM3('Filled Button').generateFile(verbose: verbose); + const ButtonTemplateM3('Filled Tonal Button').generateFile(verbose: verbose); + const ButtonTemplateM3('Outlined Button').generateFile(verbose: verbose); + const ButtonTemplateM3('Text Button').generateFile(verbose: verbose); // const CardTemplateM3().generateFile(verbose: verbose); // const CheckboxTemplateM3().generateFile(verbose: verbose); // const ChipTemplateM3().generateFile(verbose: verbose); diff --git a/packages/material_ui/tool/gen_defaults/templates/button_template.dart b/packages/material_ui/tool/gen_defaults/templates/button_template.dart new file mode 100644 index 000000000000..147390eafadc --- /dev/null +++ b/packages/material_ui/tool/gen_defaults/templates/button_template.dart @@ -0,0 +1,317 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../data/button.dart'; +import '../data/button_elevated.dart'; +import '../data/button_filled.dart'; +import '../data/button_outlined.dart'; +import '../data/button_text.dart'; +import '../data/button_tonal.dart'; +import '../data/color_role.dart'; +import 'template.dart'; + +enum _ButtonVariant { elevated, filled, filledTonal, outlined, text } + +class ButtonTemplateM3 extends TokenTemplateM3 { + const ButtonTemplateM3(this.name); + + @override + final String name; + + @override + String get parentFilePath => switch (_variant) { + _ButtonVariant.elevated => 'elevated_button.dart', + _ButtonVariant.filled || _ButtonVariant.filledTonal => 'filled_button.dart', + _ButtonVariant.outlined => 'outlined_button.dart', + _ButtonVariant.text => 'text_button.dart', + }; + + // Some exported md.comp.button.* tokens differ from Flutter's existing M3 + // button defaults. Preserve those existing values so the defaults do not + // change during this template migration. + // TODO(QuncCccccc): Replace these values with tokens. + static const double _legacyDisabledContainerOpacity = 0.12; + static const double _legacyHoverElevation = 3.0; + static const double _legacyFilledHoverElevation = 1.0; + static const double _legacyIconSize = 18.0; + + _ButtonVariant get _variant => switch (name) { + 'Elevated Button' => _ButtonVariant.elevated, + 'Filled Button' => _ButtonVariant.filled, + 'Filled Tonal Button' => _ButtonVariant.filledTonal, + 'Outlined Button' => _ButtonVariant.outlined, + 'Text Button' => _ButtonVariant.text, + _ => throw UnsupportedError('Unsupported button template name: $name'), + }; + + TokenColorRole? get _containerColor => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.containerColor, + _ButtonVariant.filled => TokenButtonFilled.containerColor, + _ButtonVariant.filledTonal => TokenButtonTonal.containerColor, + _ButtonVariant.outlined || _ButtonVariant.text => null, + }; + + TokenColorRole get _labelTextColor => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.labelTextColor, + _ButtonVariant.filled => TokenButtonFilled.labelTextColor, + _ButtonVariant.filledTonal => TokenButtonTonal.labelTextColor, + _ButtonVariant.outlined => TokenColorRole.primary, + _ButtonVariant.text => TokenButtonText.labelTextColor, + }; + + TokenColorRole get _stateLayerColor => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.pressedStateLayerColor, + _ButtonVariant.filled => TokenButtonFilled.pressedStateLayerColor, + _ButtonVariant.filledTonal => TokenButtonTonal.pressedStateLayerColor, + _ButtonVariant.outlined => TokenColorRole.primary, + _ButtonVariant.text => TokenButtonText.pressedStateLayerColor, + }; + + TokenColorRole get _iconColor => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.iconColor, + _ButtonVariant.filled => TokenButtonFilled.iconColor, + _ButtonVariant.filledTonal => TokenButtonTonal.iconColor, + _ButtonVariant.outlined => TokenColorRole.primary, + _ButtonVariant.text => TokenButtonText.iconColor, + }; + + double get _disabledContainerElevation => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.disabledContainerElevation, + _ButtonVariant.filled => TokenButtonFilled.disabledContainerElevation, + _ButtonVariant.filledTonal => TokenButtonTonal.disabledContainerElevation, + _ButtonVariant.outlined || _ButtonVariant.text => TokenButton.disabledContainerElevation, + }; + + double get _pressedContainerElevation => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.pressedContainerElevation, + _ButtonVariant.filled => TokenButtonFilled.pressedContainerElevation, + _ButtonVariant.filledTonal => TokenButtonTonal.pressedContainerElevation, + _ButtonVariant.outlined || _ButtonVariant.text => TokenButton.pressedContainerElevation, + }; + + double get _focusedContainerElevation => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.focusedContainerElevation, + _ButtonVariant.filled => TokenButtonFilled.focusedContainerElevation, + _ButtonVariant.filledTonal => TokenButtonTonal.focusedContainerElevation, + _ButtonVariant.outlined || _ButtonVariant.text => TokenButton.focusedContainerElevation, + }; + + double get _containerElevation => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.containerElevation, + _ButtonVariant.filled => TokenButtonFilled.containerElevation, + _ButtonVariant.filledTonal => TokenButtonTonal.containerElevation, + _ButtonVariant.outlined || _ButtonVariant.text => TokenButton.containerElevation, + }; + + double get _hoveredContainerElevation => switch (_variant) { + _ButtonVariant.elevated => _legacyHoverElevation, + _ButtonVariant.filled || _ButtonVariant.filledTonal => _legacyFilledHoverElevation, + _ButtonVariant.outlined || _ButtonVariant.text => TokenButton.containerElevation, + }; + + double get _pressedStateLayerOpacity => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.pressedStateLayerOpacity, + _ButtonVariant.filled => TokenButtonFilled.pressedStateLayerOpacity, + _ButtonVariant.filledTonal => TokenButtonTonal.pressedStateLayerOpacity, + _ButtonVariant.outlined => TokenButtonOutlined.pressedStateLayerOpacity, + _ButtonVariant.text => TokenButtonText.pressedStateLayerOpacity, + }; + + double get _hoveredStateLayerOpacity => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.hoveredStateLayerOpacity, + _ButtonVariant.filled => TokenButtonFilled.hoveredStateLayerOpacity, + _ButtonVariant.filledTonal => TokenButtonTonal.hoveredStateLayerOpacity, + _ButtonVariant.outlined => TokenButtonOutlined.hoveredStateLayerOpacity, + _ButtonVariant.text => TokenButtonText.hoveredStateLayerOpacity, + }; + + double get _focusedStateLayerOpacity => switch (_variant) { + _ButtonVariant.elevated => TokenButtonElevated.focusedStateLayerOpacity, + _ButtonVariant.filled => TokenButtonFilled.focusedStateLayerOpacity, + _ButtonVariant.filledTonal => TokenButtonTonal.focusedStateLayerOpacity, + _ButtonVariant.outlined => TokenButtonOutlined.focusedStateLayerOpacity, + _ButtonVariant.text => TokenButtonText.focusedStateLayerOpacity, + }; + + String get _buttonTextStyle { + return textStyle(TokenButton.labelText, 'Theme.of(context).textTheme'); + } + + String get _backgroundColor { + final TokenColorRole? containerColor = _containerColor; + if (containerColor == null) { + return 'const MaterialStatePropertyAll(Colors.transparent)'; + } + return ''' +WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return ${colorWithOpacity(TokenButton.disabledContainerColor, _legacyDisabledContainerOpacity, '_colors')}; + } + return ${color(containerColor, '_colors')}; +})'''; + } + + String get _shadowColor { + return switch (_variant) { + _ButtonVariant.elevated => + 'MaterialStatePropertyAll(${color(TokenButtonElevated.containerShadowColor, '_colors')})', + _ButtonVariant.filled => + 'MaterialStatePropertyAll(${color(TokenButtonFilled.containerShadowColor, '_colors')})', + _ButtonVariant.filledTonal => + 'MaterialStatePropertyAll(${color(TokenButtonTonal.containerShadowColor, '_colors')})', + _ButtonVariant.outlined || + _ButtonVariant.text => 'const MaterialStatePropertyAll(Colors.transparent)', + }; + } + + String get _elevation { + if (_variant == _ButtonVariant.outlined || _variant == _ButtonVariant.text) { + return 'const MaterialStatePropertyAll(0.0)'; + } + return ''' +WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return ${number(_disabledContainerElevation)}; + } + if (states.contains(WidgetState.pressed)) { + return ${number(_pressedContainerElevation)}; + } + if (states.contains(WidgetState.hovered)) { + return ${number(_hoveredContainerElevation)}; + } + if (states.contains(WidgetState.focused)) { + return ${number(_focusedContainerElevation)}; + } + return ${number(_containerElevation)}; +})'''; + } + + String get _side { + if (_variant != _ButtonVariant.outlined) { + return '// No default side'; + } + return ''' +@override +WidgetStateProperty? get side => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return BorderSide(color: ${colorWithOpacity(TokenButton.disabledContainerColor, _legacyDisabledContainerOpacity, '_colors')}); + } + if (states.contains(WidgetState.focused)) { + return BorderSide(color: ${color(TokenColorRole.primary, '_colors')}); + } + return BorderSide(color: ${color(TokenColorRole.outline, '_colors')}); + });'''; + } + + @override + String generateContents(String className) => + ''' +class $className extends ButtonStyle { + $className(this.context) + : super( + animationDuration: kThemeChangeDuration, + enableFeedback: true, + alignment: Alignment.center, + ); + + final BuildContext context; + late final ColorScheme _colors = Theme.of(context).colorScheme; + + @override + WidgetStateProperty get textStyle => + MaterialStatePropertyAll($_buttonTextStyle); + + @override + WidgetStateProperty? get backgroundColor => $_backgroundColor; + + @override + WidgetStateProperty? get foregroundColor => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return ${colorWithOpacity(TokenButton.disabledLabelTextColor, TokenButton.disabledLabelTextOpacity, '_colors')}; + } + return ${color(_labelTextColor, '_colors')}; + }); + + @override + WidgetStateProperty? get overlayColor => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.pressed)) { + return ${colorWithOpacity(_stateLayerColor, _pressedStateLayerOpacity, '_colors')}; + } + if (states.contains(WidgetState.hovered)) { + return ${colorWithOpacity(_stateLayerColor, _hoveredStateLayerOpacity, '_colors')}; + } + if (states.contains(WidgetState.focused)) { + return ${colorWithOpacity(_stateLayerColor, _focusedStateLayerOpacity, '_colors')}; + } + return null; + }); + + @override + WidgetStateProperty? get shadowColor => $_shadowColor; + + @override + WidgetStateProperty? get surfaceTintColor => + const MaterialStatePropertyAll(Colors.transparent); + + @override + WidgetStateProperty? get elevation => $_elevation; + + @override + WidgetStateProperty? get padding => + MaterialStatePropertyAll(_scaledPadding(context)); + + @override + WidgetStateProperty? get minimumSize => + const MaterialStatePropertyAll(Size(64.0, ${number(TokenButton.containerHeight)})); + + // No default fixedSize + + @override + WidgetStateProperty? get iconSize => const MaterialStatePropertyAll($_legacyIconSize); + + @override + WidgetStateProperty? get iconColor { + return WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return ${colorWithOpacity(TokenButton.disabledIconColor, TokenButton.disabledIconOpacity, '_colors')}; + } + if (states.contains(WidgetState.pressed)) { + return ${color(_iconColor, '_colors')}; + } + if (states.contains(WidgetState.hovered)) { + return ${color(_iconColor, '_colors')}; + } + if (states.contains(WidgetState.focused)) { + return ${color(_iconColor, '_colors')}; + } + return ${color(_iconColor, '_colors')}; + }); + } + + @override + WidgetStateProperty? get maximumSize => const MaterialStatePropertyAll(Size.infinite); + + $_side + + @override + WidgetStateProperty? get shape => + const MaterialStatePropertyAll(${shape(TokenButton.containerShapeRound, '')}); + + @override + WidgetStateProperty? get mouseCursor => WidgetStateMouseCursor.adaptiveClickable; + + @override + VisualDensity? get visualDensity => Theme.of(context).visualDensity; + + @override + MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize; + + @override + InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory; +} +'''; +} diff --git a/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart b/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart index 8a9ab9700a23..eb635a25ca59 100644 --- a/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart +++ b/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart @@ -12,6 +12,7 @@ import '../data/typescale_emphasized.dart'; import '../templates/action_chip_template.dart'; import '../templates/app_bar_template.dart'; import '../templates/bottom_sheet_template.dart'; +import '../templates/button_template.dart'; import '../templates/template.dart'; import 'test_fixtures/test_templates.dart'; @@ -237,9 +238,99 @@ void main() { expect(contents, contains('Size? get dragHandleSize => const Size(32.0, 4.0)')); }); - test('ButtonTemplateM3 emits M3 Button defaults from tokens', () { - // Intentionally empty, will be implemented during migration. See: - // https://github.com/flutter/flutter/issues/187899 + test('ButtonTemplateM3 maps supported button names to parent files', () { + expect(const ButtonTemplateM3('Elevated Button').parentFilePath, 'elevated_button.dart'); + expect(const ButtonTemplateM3('Filled Button').parentFilePath, 'filled_button.dart'); + expect(const ButtonTemplateM3('Filled Tonal Button').parentFilePath, 'filled_button.dart'); + expect(const ButtonTemplateM3('Outlined Button').parentFilePath, 'outlined_button.dart'); + expect(const ButtonTemplateM3('Text Button').parentFilePath, 'text_button.dart'); + }); + + test('ButtonTemplateM3 emits classes for supported button names', () { + const expectedClasses = { + 'Elevated Button': '_ElevatedButtonDefaultsM3', + 'Filled Button': '_FilledButtonDefaultsM3', + 'Filled Tonal Button': '_FilledTonalButtonDefaultsM3', + 'Outlined Button': '_OutlinedButtonDefaultsM3', + 'Text Button': '_TextButtonDefaultsM3', + }; + + for (final MapEntry entry in expectedClasses.entries) { + final String contents = _generateContents(ButtonTemplateM3(entry.key)); + expect(contents, contains('class ${entry.value} extends ButtonStyle')); + } + }); + + test('ButtonTemplateM3 emits M3 ElevatedButton defaults from tokens', () { + const template = ButtonTemplateM3('Elevated Button'); + expect(template.name, 'Elevated Button'); + expect(template.parentFilePath, 'elevated_button.dart'); + + final String contents = _generateContents(template); + expect(contents, contains('class _ElevatedButtonDefaultsM3 extends ButtonStyle')); + expect( + contents, + contains('MaterialStatePropertyAll(Theme.of(context).textTheme.labelLarge)'), + ); + expect(contents, contains('return _colors.surfaceContainerLow;')); + expect(contents, contains('return _colors.primary;')); + expect(contents, contains('return _colors.onSurface.withOpacity(0.38);')); + expect(contents, contains('return 3.0;')); + expect(contents, contains('const MaterialStatePropertyAll(Size(64.0, 40.0))')); + expect(contents, contains('const MaterialStatePropertyAll(18.0)')); + expect(contents, contains('const MaterialStatePropertyAll(StadiumBorder())')); + }); + + test('ButtonTemplateM3 emits M3 FilledButton defaults from tokens', () { + const template = ButtonTemplateM3('Filled Button'); + expect(template.parentFilePath, 'filled_button.dart'); + + final String contents = _generateContents(template); + expect(contents, contains('class _FilledButtonDefaultsM3 extends ButtonStyle')); + expect(contents, contains('return _colors.primary;')); + expect(contents, contains('return _colors.onPrimary;')); + expect(contents, contains('return _colors.onPrimary.withOpacity(0.1);')); + expect(contents, contains('return 1.0;')); + expect(contents, contains('// No default side')); + }); + + test('ButtonTemplateM3 emits M3 FilledButton.tonal() defaults from tokens', () { + const template = ButtonTemplateM3('Filled Tonal Button'); + expect(template.parentFilePath, 'filled_button.dart'); + + final String contents = _generateContents(template); + expect(contents, contains('class _FilledTonalButtonDefaultsM3 extends ButtonStyle')); + expect(contents, contains('return _colors.secondaryContainer;')); + expect(contents, contains('return _colors.onSecondaryContainer;')); + expect(contents, contains('return _colors.onSecondaryContainer.withOpacity(0.1);')); + expect(contents, contains('return 1.0;')); + expect(contents, contains('// No default side')); + }); + + test('ButtonTemplateM3 emits M3 OutlinedButton defaults from tokens', () { + const template = ButtonTemplateM3('Outlined Button'); + expect(template.parentFilePath, 'outlined_button.dart'); + + final String contents = _generateContents(template); + expect(contents, contains('class _OutlinedButtonDefaultsM3 extends ButtonStyle')); + expect(contents, contains('const MaterialStatePropertyAll(Colors.transparent)')); + expect(contents, contains('return _colors.primary;')); + expect(contents, contains('return _colors.primary.withOpacity(0.1);')); + expect(contents, contains('const MaterialStatePropertyAll(0.0)')); + expect(contents, contains('return BorderSide(color: _colors.outline);')); + }); + + test('ButtonTemplateM3 emits M3 TextButton defaults from tokens', () { + const template = ButtonTemplateM3('Text Button'); + expect(template.parentFilePath, 'text_button.dart'); + + final String contents = _generateContents(template); + expect(contents, contains('class _TextButtonDefaultsM3 extends ButtonStyle')); + expect(contents, contains('const MaterialStatePropertyAll(Colors.transparent)')); + expect(contents, contains('return _colors.primary;')); + expect(contents, contains('return _colors.primary.withOpacity(0.1);')); + expect(contents, contains('const MaterialStatePropertyAll(0.0)')); + expect(contents, contains('// No default side')); }); test('CardTemplateM3 emits M3 Card defaults from tokens', () {