Unverified Commit 513ff44b authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Add `FilterChip.elevated`, `ChoiceChip.elevated`, & `ActionChip.elevated` variants (#128049)

parent 14136ae3
...@@ -26,6 +26,7 @@ import 'package:gen_defaults/bottom_sheet_template.dart'; ...@@ -26,6 +26,7 @@ import 'package:gen_defaults/bottom_sheet_template.dart';
import 'package:gen_defaults/button_template.dart'; import 'package:gen_defaults/button_template.dart';
import 'package:gen_defaults/card_template.dart'; import 'package:gen_defaults/card_template.dart';
import 'package:gen_defaults/checkbox_template.dart'; import 'package:gen_defaults/checkbox_template.dart';
import 'package:gen_defaults/chip_template.dart';
import 'package:gen_defaults/color_scheme_template.dart'; import 'package:gen_defaults/color_scheme_template.dart';
import 'package:gen_defaults/date_picker_template.dart'; import 'package:gen_defaults/date_picker_template.dart';
import 'package:gen_defaults/dialog_template.dart'; import 'package:gen_defaults/dialog_template.dart';
...@@ -138,7 +139,7 @@ Future<void> main(List<String> args) async { ...@@ -138,7 +139,7 @@ Future<void> main(List<String> args) async {
tokens['colorsLight'] = _readTokenFile('color_light.json'); tokens['colorsLight'] = _readTokenFile('color_light.json');
tokens['colorsDark'] = _readTokenFile('color_dark.json'); tokens['colorsDark'] = _readTokenFile('color_dark.json');
ActionChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile(); ChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile();
ActionChipTemplate('ActionChip', '$materialLib/action_chip.dart', tokens).updateFile(); ActionChipTemplate('ActionChip', '$materialLib/action_chip.dart', tokens).updateFile();
AppBarTemplate('AppBar', '$materialLib/app_bar.dart', tokens).updateFile(); AppBarTemplate('AppBar', '$materialLib/app_bar.dart', tokens).updateFile();
BottomAppBarTemplate('BottomAppBar', '$materialLib/bottom_app_bar.dart', tokens).updateFile(); BottomAppBarTemplate('BottomAppBar', '$materialLib/bottom_app_bar.dart', tokens).updateFile();
......
...@@ -11,51 +11,66 @@ class ActionChipTemplate extends TokenTemplate { ...@@ -11,51 +11,66 @@ class ActionChipTemplate extends TokenTemplate {
}); });
static const String tokenGroup = 'md.comp.assist-chip'; static const String tokenGroup = 'md.comp.assist-chip';
static const String variant = '.flat'; static const String flatVariant = '.flat';
static const String elevatedVariant = '.elevated';
@override @override
String generate() => ''' String generate() => '''
class _${blockName}DefaultsM3 extends ChipThemeData { class _${blockName}DefaultsM3 extends ChipThemeData {
_${blockName}DefaultsM3(this.context, this.isEnabled) _${blockName}DefaultsM3(this.context, this.isEnabled, this._chipVariant)
: super( : super(
elevation: ${elevation("$tokenGroup$variant.container")},
shape: ${shape("$tokenGroup.container")}, shape: ${shape("$tokenGroup.container")},
showCheckmark: true, showCheckmark: true,
); );
final BuildContext context; final BuildContext context;
final bool isEnabled; final bool isEnabled;
final _ChipVariant _chipVariant;
late final ColorScheme _colors = Theme.of(context).colorScheme; late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme; late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
double? get elevation => _chipVariant == _ChipVariant.flat
? ${elevation("$tokenGroup$flatVariant.container")}
: isEnabled ? ${elevation("$tokenGroup$elevatedVariant.container")} : ${elevation("$tokenGroup$elevatedVariant.disabled.container")};
@override
double? get pressElevation => ${elevation("$tokenGroup$elevatedVariant.pressed.container")};
@override @override
TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")};
@override @override
Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")}; Color? get backgroundColor => ${componentColor("$tokenGroup$flatVariant.container")};
@override @override
Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")}; Color? get shadowColor => _chipVariant == _ChipVariant.flat
? ${colorOrTransparent("$tokenGroup$flatVariant.container.shadow-color")}
: ${colorOrTransparent("$tokenGroup$elevatedVariant.container.shadow-color")};
@override @override
Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")};
@override @override
Color? get selectedColor => ${componentColor("$tokenGroup$variant.selected.container")}; Color? get selectedColor => ${componentColor("$tokenGroup$flatVariant.selected.container")};
@override @override
Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")};
@override @override
Color? get disabledColor => ${componentColor("$tokenGroup$variant.disabled.container")}; Color? get disabledColor => _chipVariant == _ChipVariant.flat
? ${componentColor("$tokenGroup$flatVariant.disabled.container")}
: ${componentColor("$tokenGroup$elevatedVariant.disabled.container")};
@override @override
Color? get deleteIconColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; Color? get deleteIconColor => ${color("$tokenGroup.with-icon.selected.icon.color")};
@override @override
BorderSide? get side => isEnabled BorderSide? get side => _chipVariant == _ChipVariant.flat
? ${border('$tokenGroup$variant.outline')} ? isEnabled
: ${border('$tokenGroup$variant.disabled.outline')}; ? ${border('$tokenGroup$flatVariant.outline')}
: ${border('$tokenGroup$flatVariant.disabled.outline')}
: const BorderSide(color: Colors.transparent);
@override @override
IconThemeData? get iconTheme => IconThemeData( IconThemeData? get iconTheme => IconThemeData(
......
// 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 ChipTemplate extends TokenTemplate {
const ChipTemplate(super.blockName, super.fileName, super.tokens, {
super.colorSchemePrefix = '_colors.',
super.textThemePrefix = '_textTheme.'
});
static const String tokenGroup = 'md.comp.assist-chip';
static const String variant = '.flat';
@override
String generate() => '''
class _${blockName}DefaultsM3 extends ChipThemeData {
_${blockName}DefaultsM3(this.context, this.isEnabled)
: super(
elevation: ${elevation("$tokenGroup$variant.container")},
shape: ${shape("$tokenGroup.container")},
showCheckmark: true,
);
final BuildContext context;
final bool isEnabled;
late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")};
@override
Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")};
@override
Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")};
@override
Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")};
@override
Color? get selectedColor => ${componentColor("$tokenGroup$variant.selected.container")};
@override
Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")};
@override
Color? get disabledColor => ${componentColor("$tokenGroup$variant.disabled.container")};
@override
Color? get deleteIconColor => ${color("$tokenGroup.with-icon.selected.icon.color")};
@override
BorderSide? get side => isEnabled
? ${border('$tokenGroup$variant.outline')}
: ${border('$tokenGroup$variant.disabled.outline')};
@override
IconThemeData? get iconTheme => IconThemeData(
color: isEnabled
? ${color("$tokenGroup.with-icon.icon.color")}
: ${color("$tokenGroup.with-icon.disabled.icon.color")},
size: ${tokens["$tokenGroup.with-icon.icon.size"]},
);
@override
EdgeInsetsGeometry? get padding => const EdgeInsets.all(8.0);
/// The chip at text scale 1 starts with 8px on each side and as text scaling
/// gets closer to 2, the label padding is linearly interpolated from 8px to 4px.
/// Once the widget has a text scaling of 2 or higher than the label padding
/// remains 4px.
@override
EdgeInsetsGeometry? get labelPadding => EdgeInsets.lerp(
const EdgeInsets.symmetric(horizontal: 8.0),
const EdgeInsets.symmetric(horizontal: 4.0),
clampDouble(MediaQuery.textScaleFactorOf(context) - 1.0, 0.0, 1.0),
)!;
}
''';
}
...@@ -11,14 +11,18 @@ class FilterChipTemplate extends TokenTemplate { ...@@ -11,14 +11,18 @@ class FilterChipTemplate extends TokenTemplate {
}); });
static const String tokenGroup = 'md.comp.filter-chip'; static const String tokenGroup = 'md.comp.filter-chip';
static const String variant = '.flat'; static const String flatVariant = '.flat';
static const String elevatedVariant = '.elevated';
@override @override
String generate() => ''' String generate() => '''
class _${blockName}DefaultsM3 extends ChipThemeData { class _${blockName}DefaultsM3 extends ChipThemeData {
_${blockName}DefaultsM3(this.context, this.isEnabled, this.isSelected) _${blockName}DefaultsM3(
: super( this.context,
elevation: ${elevation("$tokenGroup$variant.container")}, this.isEnabled,
this.isSelected,
this._chipVariant,
) : super(
shape: ${shape("$tokenGroup.container")}, shape: ${shape("$tokenGroup.container")},
showCheckmark: true, showCheckmark: true,
); );
...@@ -26,42 +30,59 @@ class _${blockName}DefaultsM3 extends ChipThemeData { ...@@ -26,42 +30,59 @@ class _${blockName}DefaultsM3 extends ChipThemeData {
final BuildContext context; final BuildContext context;
final bool isEnabled; final bool isEnabled;
final bool isSelected; final bool isSelected;
final _ChipVariant _chipVariant;
late final ColorScheme _colors = Theme.of(context).colorScheme; late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme; late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
double? get elevation => _chipVariant == _ChipVariant.flat
? ${elevation("$tokenGroup$flatVariant.container")}
: isEnabled ? ${elevation("$tokenGroup$elevatedVariant.container")} : ${elevation("$tokenGroup$elevatedVariant.disabled.container")};
@override
double? get pressElevation => ${elevation("$tokenGroup$elevatedVariant.pressed.container")};
@override @override
TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")};
@override @override
Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")}; Color? get backgroundColor => ${componentColor("$tokenGroup$flatVariant.container")};
@override @override
Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")}; Color? get shadowColor => _chipVariant == _ChipVariant.flat
? ${colorOrTransparent("$tokenGroup$flatVariant.container.shadow-color")}
: ${colorOrTransparent("$tokenGroup$elevatedVariant.container.shadow-color")};
@override @override
Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")};
@override @override
Color? get selectedColor => isEnabled Color? get selectedColor => _chipVariant == _ChipVariant.flat
? ${componentColor("$tokenGroup$variant.selected.container")} ? isEnabled
: ${componentColor("$tokenGroup$variant.disabled.selected.container")}; ? ${componentColor("$tokenGroup$flatVariant.selected.container")}
: ${componentColor("$tokenGroup$flatVariant.disabled.selected.container")}
: isEnabled
? ${componentColor("$tokenGroup$elevatedVariant.selected.container")}
: ${componentColor("$tokenGroup$elevatedVariant.disabled.container")};
@override @override
Color? get checkmarkColor => ${color("$tokenGroup.with-leading-icon.selected.leading-icon.color")}; Color? get checkmarkColor => ${color("$tokenGroup.with-leading-icon.selected.leading-icon.color")};
@override @override
Color? get disabledColor => isSelected Color? get disabledColor => _chipVariant == _ChipVariant.flat
? ${componentColor("$tokenGroup$variant.disabled.selected.container")} ? isSelected
: ${componentColor("$tokenGroup$variant.disabled.unselected.container")}; ? ${componentColor("$tokenGroup$flatVariant.disabled.selected.container")}
: ${componentColor("$tokenGroup$flatVariant.disabled.unselected.container")}
: ${componentColor("$tokenGroup$elevatedVariant.disabled.container")};
@override @override
Color? get deleteIconColor => ${color("$tokenGroup.with-trailing-icon.selected.trailing-icon.color")}; Color? get deleteIconColor => ${color("$tokenGroup.with-trailing-icon.selected.trailing-icon.color")};
@override @override
BorderSide? get side => !isSelected BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected
? isEnabled ? isEnabled
? ${border('$tokenGroup$variant.unselected.outline')} ? ${border('$tokenGroup$flatVariant.unselected.outline')}
: ${border('$tokenGroup$variant.disabled.unselected.outline')} : ${border('$tokenGroup$flatVariant.disabled.unselected.outline')}
: const BorderSide(color: Colors.transparent); : const BorderSide(color: Colors.transparent);
@override @override
......
...@@ -14,6 +14,8 @@ import 'text_theme.dart'; ...@@ -14,6 +14,8 @@ import 'text_theme.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
enum _ChipVariant { flat, elevated }
/// A Material Design action chip. /// A Material Design action chip.
/// ///
/// Action chips are a set of options which trigger an action related to primary /// Action chips are a set of options which trigger an action related to primary
...@@ -89,7 +91,40 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip ...@@ -89,7 +91,40 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
this.surfaceTintColor, this.surfaceTintColor,
this.iconTheme, this.iconTheme,
}) : assert(pressElevation == null || pressElevation >= 0.0), }) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0); assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.flat;
/// Create an elevated chip that acts like a button.
///
/// The [label], [onPressed], [autofocus], and [clipBehavior] arguments must
/// not be null. The [pressElevation] and [elevation] must be null or
/// non-negative. Typically, [pressElevation] is greater than [elevation].
const ActionChip.elevated({
super.key,
this.avatar,
required this.label,
this.labelStyle,
this.labelPadding,
this.onPressed,
this.pressElevation,
this.tooltip,
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor,
this.disabledColor,
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor,
this.surfaceTintColor,
this.iconTheme,
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.elevated;
@override @override
final Widget? avatar; final Widget? avatar;
...@@ -137,11 +172,13 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip ...@@ -137,11 +172,13 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
@override @override
bool get isEnabled => onPressed != null; bool get isEnabled => onPressed != null;
final _ChipVariant _chipVariant;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
final ChipThemeData? defaults = Theme.of(context).useMaterial3 final ChipThemeData? defaults = Theme.of(context).useMaterial3
? _ActionChipDefaultsM3(context, isEnabled) ? _ActionChipDefaultsM3(context, isEnabled, _chipVariant)
: null; : null;
return RawChip( return RawChip(
defaultProperties: defaults, defaultProperties: defaults,
...@@ -180,18 +217,26 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip ...@@ -180,18 +217,26 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
// Token database version: v0_162 // Token database version: v0_162
class _ActionChipDefaultsM3 extends ChipThemeData { class _ActionChipDefaultsM3 extends ChipThemeData {
_ActionChipDefaultsM3(this.context, this.isEnabled) _ActionChipDefaultsM3(this.context, this.isEnabled, this._chipVariant)
: super( : super(
elevation: 0.0,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
showCheckmark: true, showCheckmark: true,
); );
final BuildContext context; final BuildContext context;
final bool isEnabled; final bool isEnabled;
final _ChipVariant _chipVariant;
late final ColorScheme _colors = Theme.of(context).colorScheme; late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme; late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
double? get elevation => _chipVariant == _ChipVariant.flat
? 0.0
: isEnabled ? 1.0 : 0.0;
@override
double? get pressElevation => 1.0;
@override @override
TextStyle? get labelStyle => _textTheme.labelLarge; TextStyle? get labelStyle => _textTheme.labelLarge;
...@@ -199,7 +244,9 @@ class _ActionChipDefaultsM3 extends ChipThemeData { ...@@ -199,7 +244,9 @@ class _ActionChipDefaultsM3 extends ChipThemeData {
Color? get backgroundColor => null; Color? get backgroundColor => null;
@override @override
Color? get shadowColor => Colors.transparent; Color? get shadowColor => _chipVariant == _ChipVariant.flat
? Colors.transparent
: _colors.shadow;
@override @override
Color? get surfaceTintColor => _colors.surfaceTint; Color? get surfaceTintColor => _colors.surfaceTint;
...@@ -211,15 +258,19 @@ class _ActionChipDefaultsM3 extends ChipThemeData { ...@@ -211,15 +258,19 @@ class _ActionChipDefaultsM3 extends ChipThemeData {
Color? get checkmarkColor => null; Color? get checkmarkColor => null;
@override @override
Color? get disabledColor => null; Color? get disabledColor => _chipVariant == _ChipVariant.flat
? null
: _colors.onSurface.withOpacity(0.12);
@override @override
Color? get deleteIconColor => null; Color? get deleteIconColor => null;
@override @override
BorderSide? get side => isEnabled BorderSide? get side => _chipVariant == _ChipVariant.flat
? BorderSide(color: _colors.outline) ? isEnabled
: BorderSide(color: _colors.onSurface.withOpacity(0.12)); ? BorderSide(color: _colors.outline)
: BorderSide(color: _colors.onSurface.withOpacity(0.12))
: const BorderSide(color: Colors.transparent);
@override @override
IconThemeData? get iconTheme => IconThemeData( IconThemeData? get iconTheme => IconThemeData(
......
...@@ -14,6 +14,8 @@ import 'text_theme.dart'; ...@@ -14,6 +14,8 @@ import 'text_theme.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
enum _ChipVariant { flat, elevated }
/// A Material Design choice chip. /// A Material Design choice chip.
/// ///
/// [ChoiceChip]s represent a single choice from a set. Choice chips contain /// [ChoiceChip]s represent a single choice from a set. Choice chips contain
...@@ -89,7 +91,46 @@ class ChoiceChip extends StatelessWidget ...@@ -89,7 +91,46 @@ class ChoiceChip extends StatelessWidget
this.checkmarkColor, this.checkmarkColor,
this.avatarBorder = const CircleBorder(), this.avatarBorder = const CircleBorder(),
}) : assert(pressElevation == null || pressElevation >= 0.0), }) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0); assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.flat;
/// Create an elevated chip that acts like a radio button.
///
/// The [label], [selected], [autofocus], and [clipBehavior] arguments must
/// not be null. The [pressElevation] and [elevation] must be null or
/// non-negative. Typically, [pressElevation] is greater than [elevation].
const ChoiceChip.elevated({
super.key,
this.avatar,
required this.label,
this.labelStyle,
this.labelPadding,
this.onSelected,
this.pressElevation,
required this.selected,
this.selectedColor,
this.disabledColor,
this.tooltip,
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor,
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor,
this.surfaceTintColor,
this.iconTheme,
this.selectedShadowColor,
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.elevated;
@override @override
final Widget? avatar; final Widget? avatar;
...@@ -149,12 +190,14 @@ class ChoiceChip extends StatelessWidget ...@@ -149,12 +190,14 @@ class ChoiceChip extends StatelessWidget
@override @override
bool get isEnabled => onSelected != null; bool get isEnabled => onSelected != null;
final _ChipVariant _chipVariant;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
final ChipThemeData chipTheme = ChipTheme.of(context); final ChipThemeData chipTheme = ChipTheme.of(context);
final ChipThemeData? defaults = Theme.of(context).useMaterial3 final ChipThemeData? defaults = Theme.of(context).useMaterial3
? _ChoiceChipDefaultsM3(context, isEnabled, selected) ? _ChoiceChipDefaultsM3(context, isEnabled, selected, _chipVariant)
: null; : null;
return RawChip( return RawChip(
defaultProperties: defaults, defaultProperties: defaults,
...@@ -200,9 +243,12 @@ class ChoiceChip extends StatelessWidget ...@@ -200,9 +243,12 @@ class ChoiceChip extends StatelessWidget
// Token database version: v0_162 // Token database version: v0_162
class _ChoiceChipDefaultsM3 extends ChipThemeData { class _ChoiceChipDefaultsM3 extends ChipThemeData {
_ChoiceChipDefaultsM3(this.context, this.isEnabled, this.isSelected) _ChoiceChipDefaultsM3(
: super( this.context,
elevation: 0.0, this.isEnabled,
this.isSelected,
this._chipVariant,
) : super(
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
showCheckmark: true, showCheckmark: true,
); );
...@@ -210,9 +256,18 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData { ...@@ -210,9 +256,18 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData {
final BuildContext context; final BuildContext context;
final bool isEnabled; final bool isEnabled;
final bool isSelected; final bool isSelected;
final _ChipVariant _chipVariant;
late final ColorScheme _colors = Theme.of(context).colorScheme; late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme; late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
double? get elevation => _chipVariant == _ChipVariant.flat
? 0.0
: isEnabled ? 1.0 : 0.0;
@override
double? get pressElevation => 1.0;
@override @override
TextStyle? get labelStyle => _textTheme.labelLarge; TextStyle? get labelStyle => _textTheme.labelLarge;
...@@ -220,29 +275,37 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData { ...@@ -220,29 +275,37 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData {
Color? get backgroundColor => null; Color? get backgroundColor => null;
@override @override
Color? get shadowColor => Colors.transparent; Color? get shadowColor => _chipVariant == _ChipVariant.flat
? Colors.transparent
: _colors.shadow;
@override @override
Color? get surfaceTintColor => _colors.surfaceTint; Color? get surfaceTintColor => _colors.surfaceTint;
@override @override
Color? get selectedColor => isEnabled Color? get selectedColor => _chipVariant == _ChipVariant.flat
? _colors.secondaryContainer ? isEnabled
: _colors.onSurface.withOpacity(0.12); ? _colors.secondaryContainer
: _colors.onSurface.withOpacity(0.12)
: isEnabled
? _colors.secondaryContainer
: _colors.onSurface.withOpacity(0.12);
@override @override
Color? get checkmarkColor => _colors.onSecondaryContainer; Color? get checkmarkColor => _colors.onSecondaryContainer;
@override @override
Color? get disabledColor => isSelected Color? get disabledColor => _chipVariant == _ChipVariant.flat
? _colors.onSurface.withOpacity(0.12) ? isSelected
: null; ? _colors.onSurface.withOpacity(0.12)
: null
: _colors.onSurface.withOpacity(0.12);
@override @override
Color? get deleteIconColor => _colors.onSecondaryContainer; Color? get deleteIconColor => _colors.onSecondaryContainer;
@override @override
BorderSide? get side => !isSelected BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected
? isEnabled ? isEnabled
? BorderSide(color: _colors.outline) ? BorderSide(color: _colors.outline)
: BorderSide(color: _colors.onSurface.withOpacity(0.12)) : BorderSide(color: _colors.onSurface.withOpacity(0.12))
......
...@@ -14,6 +14,8 @@ import 'text_theme.dart'; ...@@ -14,6 +14,8 @@ import 'text_theme.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
enum _ChipVariant { flat, elevated }
/// A Material Design filter chip. /// A Material Design filter chip.
/// ///
/// Filter chips use tags or descriptive words as a way to filter content. /// Filter chips use tags or descriptive words as a way to filter content.
...@@ -91,7 +93,46 @@ class FilterChip extends StatelessWidget ...@@ -91,7 +93,46 @@ class FilterChip extends StatelessWidget
this.checkmarkColor, this.checkmarkColor,
this.avatarBorder = const CircleBorder(), this.avatarBorder = const CircleBorder(),
}) : assert(pressElevation == null || pressElevation >= 0.0), }) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0); assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.flat;
/// Create an elevated chip that acts like a checkbox.
///
/// The [selected], [label], [autofocus], and [clipBehavior] arguments must
/// not be null. The [pressElevation] and [elevation] must be null or
/// non-negative. Typically, [pressElevation] is greater than [elevation].
const FilterChip.elevated({
super.key,
this.avatar,
required this.label,
this.labelStyle,
this.labelPadding,
this.selected = false,
required this.onSelected,
this.pressElevation,
this.disabledColor,
this.selectedColor,
this.tooltip,
this.side,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.backgroundColor,
this.padding,
this.visualDensity,
this.materialTapTargetSize,
this.elevation,
this.shadowColor,
this.surfaceTintColor,
this.iconTheme,
this.selectedShadowColor,
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.elevated;
@override @override
final Widget? avatar; final Widget? avatar;
...@@ -151,11 +192,13 @@ class FilterChip extends StatelessWidget ...@@ -151,11 +192,13 @@ class FilterChip extends StatelessWidget
@override @override
bool get isEnabled => onSelected != null; bool get isEnabled => onSelected != null;
final _ChipVariant _chipVariant;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
final ChipThemeData? defaults = Theme.of(context).useMaterial3 final ChipThemeData? defaults = Theme.of(context).useMaterial3
? _FilterChipDefaultsM3(context, isEnabled, selected) ? _FilterChipDefaultsM3(context, isEnabled, selected, _chipVariant)
: null; : null;
return RawChip( return RawChip(
defaultProperties: defaults, defaultProperties: defaults,
...@@ -200,9 +243,12 @@ class FilterChip extends StatelessWidget ...@@ -200,9 +243,12 @@ class FilterChip extends StatelessWidget
// Token database version: v0_162 // Token database version: v0_162
class _FilterChipDefaultsM3 extends ChipThemeData { class _FilterChipDefaultsM3 extends ChipThemeData {
_FilterChipDefaultsM3(this.context, this.isEnabled, this.isSelected) _FilterChipDefaultsM3(
: super( this.context,
elevation: 0.0, this.isEnabled,
this.isSelected,
this._chipVariant,
) : super(
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
showCheckmark: true, showCheckmark: true,
); );
...@@ -210,9 +256,18 @@ class _FilterChipDefaultsM3 extends ChipThemeData { ...@@ -210,9 +256,18 @@ class _FilterChipDefaultsM3 extends ChipThemeData {
final BuildContext context; final BuildContext context;
final bool isEnabled; final bool isEnabled;
final bool isSelected; final bool isSelected;
final _ChipVariant _chipVariant;
late final ColorScheme _colors = Theme.of(context).colorScheme; late final ColorScheme _colors = Theme.of(context).colorScheme;
late final TextTheme _textTheme = Theme.of(context).textTheme; late final TextTheme _textTheme = Theme.of(context).textTheme;
@override
double? get elevation => _chipVariant == _ChipVariant.flat
? 0.0
: isEnabled ? 1.0 : 0.0;
@override
double? get pressElevation => 1.0;
@override @override
TextStyle? get labelStyle => _textTheme.labelLarge; TextStyle? get labelStyle => _textTheme.labelLarge;
...@@ -220,29 +275,37 @@ class _FilterChipDefaultsM3 extends ChipThemeData { ...@@ -220,29 +275,37 @@ class _FilterChipDefaultsM3 extends ChipThemeData {
Color? get backgroundColor => null; Color? get backgroundColor => null;
@override @override
Color? get shadowColor => Colors.transparent; Color? get shadowColor => _chipVariant == _ChipVariant.flat
? Colors.transparent
: _colors.shadow;
@override @override
Color? get surfaceTintColor => _colors.surfaceTint; Color? get surfaceTintColor => _colors.surfaceTint;
@override @override
Color? get selectedColor => isEnabled Color? get selectedColor => _chipVariant == _ChipVariant.flat
? _colors.secondaryContainer ? isEnabled
: _colors.onSurface.withOpacity(0.12); ? _colors.secondaryContainer
: _colors.onSurface.withOpacity(0.12)
: isEnabled
? _colors.secondaryContainer
: _colors.onSurface.withOpacity(0.12);
@override @override
Color? get checkmarkColor => _colors.onSecondaryContainer; Color? get checkmarkColor => _colors.onSecondaryContainer;
@override @override
Color? get disabledColor => isSelected Color? get disabledColor => _chipVariant == _ChipVariant.flat
? _colors.onSurface.withOpacity(0.12) ? isSelected
: null; ? _colors.onSurface.withOpacity(0.12)
: null
: _colors.onSurface.withOpacity(0.12);
@override @override
Color? get deleteIconColor => _colors.onSecondaryContainer; Color? get deleteIconColor => _colors.onSecondaryContainer;
@override @override
BorderSide? get side => !isSelected BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected
? isEnabled ? isEnabled
? BorderSide(color: _colors.outline) ? BorderSide(color: _colors.outline)
: BorderSide(color: _colors.onSurface.withOpacity(0.12)) : BorderSide(color: _colors.onSurface.withOpacity(0.12))
......
...@@ -26,6 +26,24 @@ Widget wrapForChip({ ...@@ -26,6 +26,24 @@ Widget wrapForChip({
); );
} }
Material getMaterial(WidgetTester tester) {
return tester.widget<Material>(
find.descendant(
of: find.byType(ActionChip),
matching: find.byType(Material),
),
);
}
DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) {
return tester.widget(
find.ancestor(
of: find.text(labelText),
matching: find.byType(DefaultTextStyle),
).first,
);
}
void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) {
final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material)); final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material));
// There should be two Material widgets, first Material is from the "_wrapForChip" and // There should be two Material widgets, first Material is from the "_wrapForChip" and
...@@ -36,6 +54,148 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { ...@@ -36,6 +54,148 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) {
} }
void main() { void main() {
testWidgets('ActionChip defaults', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true);
const String label = 'action chip';
// Test enabled ActionChip defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Center(
child: ActionChip(
onPressed: () {},
label: const Text(label),
),
),
),
),
);
// Test default chip size.
expect(tester.getSize(find.byType(ActionChip)), const Size(190.0, 48.0));
// Test default label style.
expect(
getLabelStyle(tester, label).style.color!.value,
theme.textTheme.labelLarge!.color!.value,
);
Material chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 0);
expect(chipMaterial.shadowColor, Colors.transparent);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: theme.colorScheme.outline),
),
);
ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, null);
// Test disabled ActionChip defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Material(
child: ActionChip(
label: Text(label),
),
),
),
);
await tester.pumpAndSettle();
chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 0);
expect(chipMaterial.shadowColor, Colors.transparent);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)),
),
);
decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, null);
});
testWidgets('ActionChip.elevated defaults', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true);
const String label = 'action chip';
// Test enabled ActionChip defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Center(
child: ActionChip.elevated(
onPressed: () {},
label: const Text(label),
),
),
),
),
);
// Test default chip size.
expect(tester.getSize(find.byType(ActionChip)), const Size(190.0, 48.0));
// Test default label style.
expect(
getLabelStyle(tester, label).style.color!.value,
theme.textTheme.labelLarge!.color!.value,
);
Material chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 1);
expect(chipMaterial.shadowColor, theme.colorScheme.shadow);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: Colors.transparent),
),
);
ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, null);
// Test disabled ActionChip.elevated defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Material(
child: ActionChip.elevated(
label: Text(label),
),
),
),
);
await tester.pumpAndSettle();
chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 0);
expect(chipMaterial.shadowColor, theme.colorScheme.shadow);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: Colors.transparent),
),
);
decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12));
});
testWidgetsWithLeakTracking('ActionChip can be tapped', (WidgetTester tester) async { testWidgetsWithLeakTracking('ActionChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
......
...@@ -84,7 +84,289 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { ...@@ -84,7 +84,289 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) {
expect(materials.last.clipBehavior, clipBehavior); expect(materials.last.clipBehavior, clipBehavior);
} }
Material getMaterial(WidgetTester tester) {
return tester.widget<Material>(
find.descendant(
of: find.byType(FilterChip),
matching: find.byType(Material),
),
);
}
DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) {
return tester.widget(
find.ancestor(
of: find.text(labelText),
matching: find.byType(DefaultTextStyle),
).first,
);
}
void main() { void main() {
testWidgets('FilterChip defaults', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true);
const String label = 'filter chip';
// Test enabled FilterChip defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Center(
child: FilterChip(
onSelected: (bool valueChanged) { },
label: const Text(label),
),
),
),
),
);
// Test default chip size.
expect(tester.getSize(find.byType(FilterChip)), const Size(190.0, 48.0));
// Test default label style.
expect(
getLabelStyle(tester, label).style.color!.value,
theme.textTheme.labelLarge!.color!.value,
);
Material chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 0);
expect(chipMaterial.shadowColor, Colors.transparent);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: theme.colorScheme.outline),
),
);
ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, null);
// Test disabled FilterChip defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Material(
child: FilterChip(
onSelected: null,
label: Text(label),
),
),
),
);
await tester.pumpAndSettle();
chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 0);
expect(chipMaterial.shadowColor, Colors.transparent);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)),
),
);
decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, null);
// Test selected enabled FilterChip defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: FilterChip(
selected: true,
onSelected: (bool valueChanged) { },
label: const Text(label),
),
),
),
);
await tester.pumpAndSettle();
chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 0);
expect(chipMaterial.shadowColor, null);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: Colors.transparent),
),
);
decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, theme.colorScheme.secondaryContainer);
// Test selected disabled FilterChip defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Material(
child: FilterChip(
selected: true,
onSelected: null,
label: Text(label),
),
),
),
);
await tester.pumpAndSettle();
chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 0);
expect(chipMaterial.shadowColor, null);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: Colors.transparent),
),
);
decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12));
});
testWidgets('FilterChip.elevated defaults', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true);
const String label = 'filter chip';
// Test enabled FilterChip.elevated defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Center(
child: FilterChip.elevated(
onSelected: (bool valueChanged) { },
label: const Text(label),
),
),
),
),
);
// Test default chip size.
expect(tester.getSize(find.byType(FilterChip)), const Size(190.0, 48.0));
// Test default label style.
expect(
getLabelStyle(tester, 'filter chip').style.color!.value,
theme.textTheme.labelLarge!.color!.value,
);
Material chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 1);
expect(chipMaterial.shadowColor, theme.colorScheme.shadow);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: Colors.transparent),
),
);
ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, null);
// Test disabled FilterChip.elevated defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Material(
child: FilterChip.elevated(
onSelected: null,
label: Text(label),
),
),
),
);
await tester.pumpAndSettle();
chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 0);
expect(chipMaterial.shadowColor, theme.colorScheme.shadow);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: Colors.transparent),
),
);
decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12));
// Test selected enabled FilterChip.elevated defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: FilterChip.elevated(
selected: true,
onSelected: (bool valueChanged) { },
label: const Text(label),
),
),
),
);
await tester.pumpAndSettle();
chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 1);
expect(chipMaterial.shadowColor, null);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: Colors.transparent),
),
);
decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, theme.colorScheme.secondaryContainer);
// Test selected disabled FilterChip.elevated defaults.
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Material(
child: FilterChip.elevated(
selected: true,
onSelected: null,
label: Text(label),
),
),
),
);
await tester.pumpAndSettle();
chipMaterial = getMaterial(tester);
expect(chipMaterial.elevation, 0);
expect(chipMaterial.shadowColor, null);
expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
expect(
chipMaterial.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
side: BorderSide(color: Colors.transparent),
),
);
decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration;
expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12));
});
testWidgets('FilterChip can be tapped', (WidgetTester tester) async { testWidgets('FilterChip can be tapped', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment