mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Related https://github.com/flutter/flutter/issues/131676 ## Description #### Fix default input text style for `DropdownMenu`  ### Fix default text style for `MenuAnchor`'s menu items (which `DropdownMenu` uses for menu items)  ### Default `DropdownMenu` Input text style  ### Default `DropdownMenu` menu item text style  ### Default `MenuAnchor` menu item text style  ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; /// Flutter code sample for [DropdownMenu]s. The first dropdown menu has an outlined border. void main() => runApp(const DropdownMenuExample()); class DropdownMenuExample extends StatefulWidget { const DropdownMenuExample({super.key}); @override State<DropdownMenuExample> createState() => _DropdownMenuExampleState(); } class _DropdownMenuExampleState extends State<DropdownMenuExample> { final TextEditingController colorController = TextEditingController(); final TextEditingController iconController = TextEditingController(); ColorLabel? selectedColor; IconLabel? selectedIcon; @override Widget build(BuildContext context) { final List<DropdownMenuEntry<ColorLabel>> colorEntries = <DropdownMenuEntry<ColorLabel>>[]; for (final ColorLabel color in ColorLabel.values) { colorEntries.add( DropdownMenuEntry<ColorLabel>( value: color, label: color.label, enabled: color.label != 'Grey'), ); } final List<DropdownMenuEntry<IconLabel>> iconEntries = <DropdownMenuEntry<IconLabel>>[]; for (final IconLabel icon in IconLabel.values) { iconEntries .add(DropdownMenuEntry<IconLabel>(value: icon, label: icon.label)); } return MaterialApp( theme: ThemeData( useMaterial3: true, colorSchemeSeed: Colors.green, // textTheme: const TextTheme( // bodyLarge: TextStyle( // fontWeight: FontWeight.bold, // fontStyle: FontStyle.italic, // decoration: TextDecoration.underline, // ), // ), ), home: Scaffold( body: SafeArea( child: Column( children: <Widget>[ const Text('DropdownMenus'), Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ DropdownMenu<ColorLabel>( controller: colorController, label: const Text('Color'), dropdownMenuEntries: colorEntries, onSelected: (ColorLabel? color) { setState(() { selectedColor = color; }); }, ), const SizedBox(width: 20), DropdownMenu<IconLabel>( controller: iconController, enableFilter: true, leadingIcon: const Icon(Icons.search), label: const Text('Icon'), dropdownMenuEntries: iconEntries, inputDecorationTheme: const InputDecorationTheme( filled: true, contentPadding: EdgeInsets.symmetric(vertical: 5.0), ), onSelected: (IconLabel? icon) { setState(() { selectedIcon = icon; }); }, ), ], ), ), const Text('Plain TextFields'), Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ SizedBox( width: 150, child: TextField( controller: TextEditingController(text: 'Blue'), decoration: const InputDecoration( suffixIcon: Icon(Icons.arrow_drop_down), labelText: 'Color', border: OutlineInputBorder(), )), ), const SizedBox(width: 20), SizedBox( width: 150, child: TextField( controller: TextEditingController(text: 'Smile'), decoration: const InputDecoration( prefixIcon: Icon(Icons.search), suffixIcon: Icon(Icons.arrow_drop_down), filled: true, labelText: 'Icon', )), ), ], ), ), if (selectedColor != null && selectedIcon != null) Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You selected a ${selectedColor?.label} ${selectedIcon?.label}'), Padding( padding: const EdgeInsets.symmetric(horizontal: 5), child: Icon( selectedIcon?.icon, color: selectedColor?.color, ), ) ], ) else const Text('Please select a color and an icon.') ], ), ), ), ); } } enum ColorLabel { blue('Blue', Colors.blue), pink('Pink', Colors.pink), green('Green', Colors.green), yellow('Yellow', Colors.yellow), grey('Grey', Colors.grey); const ColorLabel(this.label, this.color); final String label; final Color color; } enum IconLabel { smile('Smile', Icons.sentiment_satisfied_outlined), cloud( 'Cloud', Icons.cloud_outlined, ), brush('Brush', Icons.brush_outlined), heart('Heart', Icons.favorite); const IconLabel(this.label, this.icon); final String label; final IconData icon; } ``` </details>
260 lines
8.5 KiB
Dart
260 lines
8.5 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'template.dart';
|
|
|
|
class MenuTemplate extends TokenTemplate {
|
|
const MenuTemplate(super.blockName, super.fileName, super.tokens, {
|
|
super.colorSchemePrefix = '_colors.',
|
|
});
|
|
|
|
@override
|
|
String generate() => '''
|
|
class _MenuBarDefaultsM3 extends MenuStyle {
|
|
_MenuBarDefaultsM3(this.context)
|
|
: super(
|
|
elevation: const MaterialStatePropertyAll<double?>(${elevation('md.comp.menu.container')}),
|
|
shape: const MaterialStatePropertyAll<OutlinedBorder>(_defaultMenuBorder),
|
|
alignment: AlignmentDirectional.bottomStart,
|
|
);
|
|
|
|
static const RoundedRectangleBorder _defaultMenuBorder =
|
|
${shape('md.comp.menu.container', '')};
|
|
|
|
final BuildContext context;
|
|
|
|
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
|
|
|
@override
|
|
MaterialStateProperty<Color?> get backgroundColor {
|
|
return MaterialStatePropertyAll<Color?>(${componentColor('md.comp.menu.container')});
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<Color?>? get shadowColor {
|
|
return MaterialStatePropertyAll<Color?>(${color('md.comp.menu.container.shadow-color')});
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<Color?>? get surfaceTintColor {
|
|
return MaterialStatePropertyAll<Color?>(${componentColor('md.comp.menu.container.surface-tint-layer')});
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<EdgeInsetsGeometry?>? get padding {
|
|
return const MaterialStatePropertyAll<EdgeInsetsGeometry>(
|
|
EdgeInsetsDirectional.symmetric(
|
|
horizontal: _kTopLevelMenuHorizontalMinPadding
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
VisualDensity get visualDensity => Theme.of(context).visualDensity;
|
|
}
|
|
|
|
class _MenuButtonDefaultsM3 extends ButtonStyle {
|
|
_MenuButtonDefaultsM3(this.context)
|
|
: super(
|
|
animationDuration: kThemeChangeDuration,
|
|
enableFeedback: true,
|
|
alignment: AlignmentDirectional.centerStart,
|
|
);
|
|
|
|
final BuildContext context;
|
|
|
|
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
|
late final TextTheme _textTheme = Theme.of(context).textTheme;
|
|
|
|
@override
|
|
MaterialStateProperty<Color?>? get backgroundColor {
|
|
return ButtonStyleButton.allOrNull<Color>(Colors.transparent);
|
|
}
|
|
|
|
// No default shadow color
|
|
|
|
// No default surface tint color
|
|
|
|
@override
|
|
MaterialStateProperty<double>? get elevation {
|
|
return ButtonStyleButton.allOrNull<double>(0.0);
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<Color?>? get foregroundColor {
|
|
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.disabled)) {
|
|
return ${componentColor('md.comp.list.list-item.disabled.label-text')};
|
|
}
|
|
if (states.contains(MaterialState.pressed)) {
|
|
return ${componentColor('md.comp.list.list-item.pressed.label-text')};
|
|
}
|
|
if (states.contains(MaterialState.hovered)) {
|
|
return ${componentColor('md.comp.list.list-item.hover.label-text')};
|
|
}
|
|
if (states.contains(MaterialState.focused)) {
|
|
return ${componentColor('md.comp.list.list-item.focus.label-text')};
|
|
}
|
|
return ${componentColor('md.comp.list.list-item.label-text')};
|
|
});
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<Color?>? get iconColor {
|
|
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.disabled)) {
|
|
return ${componentColor('md.comp.list.list-item.disabled.leading-icon')};
|
|
}
|
|
if (states.contains(MaterialState.pressed)) {
|
|
return ${componentColor('md.comp.list.list-item.pressed.leading-icon.icon')};
|
|
}
|
|
if (states.contains(MaterialState.hovered)) {
|
|
return ${componentColor('md.comp.list.list-item.hover.leading-icon.icon')};
|
|
}
|
|
if (states.contains(MaterialState.focused)) {
|
|
return ${componentColor('md.comp.list.list-item.focus.leading-icon.icon')};
|
|
}
|
|
return ${componentColor('md.comp.list.list-item.leading-icon')};
|
|
});
|
|
}
|
|
|
|
// No default fixedSize
|
|
|
|
@override
|
|
MaterialStateProperty<Size>? get maximumSize {
|
|
return ButtonStyleButton.allOrNull<Size>(Size.infinite);
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<Size>? get minimumSize {
|
|
return ButtonStyleButton.allOrNull<Size>(const Size(64.0, 48.0));
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<MouseCursor?>? get mouseCursor {
|
|
return MaterialStateProperty.resolveWith(
|
|
(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.disabled)) {
|
|
return SystemMouseCursors.basic;
|
|
}
|
|
return SystemMouseCursors.click;
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<Color?>? get overlayColor {
|
|
return MaterialStateProperty.resolveWith(
|
|
(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.pressed)) {
|
|
return ${componentColor('md.comp.list.list-item.pressed.state-layer')};
|
|
}
|
|
if (states.contains(MaterialState.hovered)) {
|
|
return ${componentColor('md.comp.list.list-item.hover.state-layer')};
|
|
}
|
|
if (states.contains(MaterialState.focused)) {
|
|
return ${componentColor('md.comp.list.list-item.focus.state-layer')};
|
|
}
|
|
return Colors.transparent;
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<EdgeInsetsGeometry>? get padding {
|
|
return ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(_scaledPadding(context));
|
|
}
|
|
|
|
// No default side
|
|
|
|
@override
|
|
MaterialStateProperty<OutlinedBorder>? get shape {
|
|
return ButtonStyleButton.allOrNull<OutlinedBorder>(const RoundedRectangleBorder());
|
|
}
|
|
|
|
@override
|
|
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
|
|
|
@override
|
|
MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
|
|
|
|
@override
|
|
MaterialStateProperty<TextStyle?> get textStyle {
|
|
// TODO(tahatesser): This is taken from https://m3.material.io/components/menus/specs
|
|
// Update this when the token is available.
|
|
return MaterialStatePropertyAll<TextStyle?>(_textTheme.labelLarge);
|
|
}
|
|
|
|
@override
|
|
VisualDensity? get visualDensity => Theme.of(context).visualDensity;
|
|
|
|
// The horizontal padding number comes from the spec.
|
|
EdgeInsetsGeometry _scaledPadding(BuildContext context) {
|
|
VisualDensity visualDensity = Theme.of(context).visualDensity;
|
|
// When horizontal VisualDensity is greater than zero, set it to zero
|
|
// because the [ButtonStyleButton] has already handle the padding based on the density.
|
|
// However, the [ButtonStyleButton] doesn't allow the [VisualDensity] adjustment
|
|
// to reduce the width of the left/right padding, so we need to handle it here if
|
|
// the density is less than zero, such as on desktop platforms.
|
|
if (visualDensity.horizontal > 0) {
|
|
visualDensity = VisualDensity(vertical: visualDensity.vertical);
|
|
}
|
|
return ButtonStyleButton.scaledPadding(
|
|
EdgeInsets.symmetric(horizontal: math.max(
|
|
_kMenuViewPadding,
|
|
_kLabelItemDefaultSpacing + visualDensity.baseSizeAdjustment.dx,
|
|
)),
|
|
EdgeInsets.symmetric(horizontal: math.max(
|
|
_kMenuViewPadding,
|
|
8 + visualDensity.baseSizeAdjustment.dx,
|
|
)),
|
|
const EdgeInsets.symmetric(horizontal: _kMenuViewPadding),
|
|
MediaQuery.maybeTextScaleFactorOf(context) ?? 1,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MenuDefaultsM3 extends MenuStyle {
|
|
_MenuDefaultsM3(this.context)
|
|
: super(
|
|
elevation: const MaterialStatePropertyAll<double?>(${elevation('md.comp.menu.container')}),
|
|
shape: const MaterialStatePropertyAll<OutlinedBorder>(_defaultMenuBorder),
|
|
alignment: AlignmentDirectional.topEnd,
|
|
);
|
|
|
|
static const RoundedRectangleBorder _defaultMenuBorder =
|
|
${shape('md.comp.menu.container', '')};
|
|
|
|
final BuildContext context;
|
|
|
|
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
|
|
|
@override
|
|
MaterialStateProperty<Color?> get backgroundColor {
|
|
return MaterialStatePropertyAll<Color?>(${componentColor('md.comp.menu.container')});
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<Color?>? get surfaceTintColor {
|
|
return MaterialStatePropertyAll<Color?>(${componentColor('md.comp.menu.container.surface-tint-layer')});
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<Color?>? get shadowColor {
|
|
return MaterialStatePropertyAll<Color?>(${color('md.comp.menu.container.shadow-color')});
|
|
}
|
|
|
|
@override
|
|
MaterialStateProperty<EdgeInsetsGeometry?>? get padding {
|
|
return const MaterialStatePropertyAll<EdgeInsetsGeometry>(
|
|
EdgeInsetsDirectional.symmetric(vertical: _kMenuVerticalMinPadding),
|
|
);
|
|
}
|
|
|
|
@override
|
|
VisualDensity get visualDensity => Theme.of(context).visualDensity;
|
|
}
|
|
''';
|
|
}
|