Skip to content

Commit 4a0f261

Browse files
authored
Add Card.filled and Card.outlined factory methods (#136229)
Fixes #119401 This PR is to: * add `Card.filled` and `Card.outlined` factory methods so that we can use tokens for these two types of cards to generate default theme instead of providing hard-corded values in example. * update card.2.dart example. * add test file for card.2.dart example. * fix some mismatch caused by editing the auto-generated defaults by hand in navigation_bar.dart and navigation_drawer.dart.
1 parent b47e4c4 commit 4a0f261

File tree

11 files changed

+358
-106
lines changed

11 files changed

+358
-106
lines changed

dev/bots/check_code_samples.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ class SampleChecker {
261261
}
262262
}
263263

264-
// These tests are known to be missing. They should all eventually be
264+
// These tests are known to be missing. They should all eventually be
265265
// implemented, but until they are we allow them, so that we can catch any new
266266
// examples that are added without tests.
267267
//
@@ -282,7 +282,6 @@ final Set<String> _knownMissingTests = <String>{
282282
'examples/api/test/material/text_field/text_field.1_test.dart',
283283
'examples/api/test/material/button_style/button_style.0_test.dart',
284284
'examples/api/test/material/range_slider/range_slider.0_test.dart',
285-
'examples/api/test/material/card/card.2_test.dart',
286285
'examples/api/test/material/card/card.0_test.dart',
287286
'examples/api/test/material/selection_container/selection_container_disabled.0_test.dart',
288287
'examples/api/test/material/selection_container/selection_container.0_test.dart',

dev/tools/gen_defaults/bin/gen_defaults.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ Future<void> main(List<String> args) async {
112112
ButtonTemplate('md.comp.filled-tonal-button', 'FilledTonalButton', '$materialLib/filled_button.dart', tokens).updateFile();
113113
ButtonTemplate('md.comp.outlined-button', 'OutlinedButton', '$materialLib/outlined_button.dart', tokens).updateFile();
114114
ButtonTemplate('md.comp.text-button', 'TextButton', '$materialLib/text_button.dart', tokens).updateFile();
115-
CardTemplate('Card', '$materialLib/card.dart', tokens).updateFile();
115+
CardTemplate('md.comp.elevated-card', 'Card', '$materialLib/card.dart', tokens).updateFile();
116+
CardTemplate('md.comp.filled-card', 'FilledCard', '$materialLib/card.dart', tokens).updateFile();
117+
CardTemplate('md.comp.outlined-card', 'OutlinedCard', '$materialLib/card.dart', tokens).updateFile();
116118
CheckboxTemplate('Checkbox', '$materialLib/checkbox.dart', tokens).updateFile();
117119
ColorSchemeTemplate(colorLightTokens, colorDarkTokens, 'ColorScheme', '$materialLib/theme_data.dart', tokens).updateFile();
118120
DatePickerTemplate('DatePicker', '$materialLib/date_picker_theme.dart', tokens).updateFile();

dev/tools/gen_defaults/generated/used_tokens.csv

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ md.comp.filled-button.label-text.text-style,
203203
md.comp.filled-button.pressed.container.elevation,
204204
md.comp.filled-button.pressed.state-layer.color,
205205
md.comp.filled-button.pressed.state-layer.opacity,
206+
md.comp.filled-card.container.color,
207+
md.comp.filled-card.container.elevation,
208+
md.comp.filled-card.container.shadow-color,
209+
md.comp.filled-card.container.shape,
206210
md.comp.filled-icon-button.container.color,
207211
md.comp.filled-icon-button.container.shape,
208212
md.comp.filled-icon-button.container.size,
@@ -459,6 +463,13 @@ md.comp.outlined-button.outline.color,
459463
md.comp.outlined-button.outline.width,
460464
md.comp.outlined-button.pressed.state-layer.color,
461465
md.comp.outlined-button.pressed.state-layer.opacity,
466+
md.comp.outlined-card.container.color,
467+
md.comp.outlined-card.container.elevation,
468+
md.comp.outlined-card.container.shadow-color,
469+
md.comp.outlined-card.container.shape,
470+
md.comp.outlined-card.container.surface-tint-layer.color,
471+
md.comp.outlined-card.outline.color,
472+
md.comp.outlined-card.outline.width,
462473
md.comp.outlined-icon-button.container.shape,
463474
md.comp.outlined-icon-button.container.size,
464475
md.comp.outlined-icon-button.disabled.icon.color,

dev/tools/gen_defaults/lib/card_template.dart

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,49 @@
55
import 'template.dart';
66

77
class CardTemplate extends TokenTemplate {
8-
const CardTemplate(super.blockName, super.fileName, super.tokens, {
8+
const CardTemplate(this.tokenGroup, super.blockName, super.fileName, super.tokens, {
99
super.colorSchemePrefix = '_colors.',
1010
});
1111

12+
final String tokenGroup;
13+
14+
String _shape() {
15+
final String cardShape = shape('$tokenGroup.container');
16+
if (tokenAvailable('$tokenGroup.outline.color')) {
17+
return '''
18+
19+
$cardShape.copyWith(
20+
side: ${border('$tokenGroup.outline')}
21+
)''';
22+
} else {
23+
return cardShape;
24+
}
25+
}
26+
1227
@override
1328
String generate() => '''
1429
class _${blockName}DefaultsM3 extends CardTheme {
1530
_${blockName}DefaultsM3(this.context)
1631
: super(
1732
clipBehavior: Clip.none,
18-
elevation: ${elevation("md.comp.elevated-card.container")},
33+
elevation: ${elevation('$tokenGroup.container')},
1934
margin: const EdgeInsets.all(4.0),
20-
shape: ${shape("md.comp.elevated-card.container")},
2135
);
2236
2337
final BuildContext context;
2438
late final ColorScheme _colors = Theme.of(context).colorScheme;
2539
2640
@override
27-
Color? get color => ${componentColor("md.comp.elevated-card.container")};
41+
Color? get color => ${componentColor('$tokenGroup.container')};
42+
43+
@override
44+
Color? get shadowColor => ${colorOrTransparent('$tokenGroup.container.shadow-color')};
2845
2946
@override
30-
Color? get shadowColor => ${colorOrTransparent("md.comp.elevated-card.container.shadow-color")};
47+
Color? get surfaceTintColor => ${colorOrTransparent('$tokenGroup.container.surface-tint-layer.color')};
3148
3249
@override
33-
Color? get surfaceTintColor => ${colorOrTransparent("md.comp.elevated-card.container.surface-tint-layer.color")};
50+
ShapeBorder? get shape =>${_shape()};
3451
}
3552
''';
3653
}

dev/tools/gen_defaults/lib/navigation_bar_template.dart

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ class _${blockName}DefaultsM3 extends NavigationBarThemeData {
3434
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
3535
return IconThemeData(
3636
size: ${getToken("md.comp.navigation-bar.icon.size")},
37-
color: states.contains(MaterialState.selected)
38-
? ${componentColor("md.comp.navigation-bar.active.icon")}
39-
: ${componentColor("md.comp.navigation-bar.inactive.icon")},
37+
color: states.contains(MaterialState.disabled)
38+
? _colors.onSurfaceVariant.withOpacity(0.38)
39+
: states.contains(MaterialState.selected)
40+
? ${componentColor("md.comp.navigation-bar.active.icon")}
41+
: ${componentColor("md.comp.navigation-bar.inactive.icon")},
4042
);
4143
});
4244
}
@@ -47,9 +49,12 @@ class _${blockName}DefaultsM3 extends NavigationBarThemeData {
4749
@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
4850
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
4951
final TextStyle style = ${textStyle("md.comp.navigation-bar.label-text")}!;
50-
return style.apply(color: states.contains(MaterialState.selected)
51-
? ${componentColor("md.comp.navigation-bar.active.label-text")}
52-
: ${componentColor("md.comp.navigation-bar.inactive.label-text")}
52+
return style.apply(
53+
color: states.contains(MaterialState.disabled)
54+
? _colors.onSurfaceVariant.withOpacity(0.38)
55+
: states.contains(MaterialState.selected)
56+
? ${componentColor("md.comp.navigation-bar.active.label-text")}
57+
: ${componentColor("md.comp.navigation-bar.inactive.label-text")}
5358
);
5459
});
5560
}

dev/tools/gen_defaults/lib/navigation_drawer_template.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ class _${blockName}DefaultsM3 extends NavigationDrawerThemeData {
4242
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
4343
return IconThemeData(
4444
size: ${getToken("md.comp.navigation-drawer.icon.size")},
45-
color: states.contains(MaterialState.selected)
45+
color: states.contains(MaterialState.disabled)
46+
? _colors.onSurfaceVariant.withOpacity(0.38)
47+
: states.contains(MaterialState.selected)
4648
? ${componentColor("md.comp.navigation-drawer.active.icon")}
4749
: ${componentColor("md.comp.navigation-drawer.inactive.icon")},
4850
);
@@ -54,7 +56,9 @@ class _${blockName}DefaultsM3 extends NavigationDrawerThemeData {
5456
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
5557
final TextStyle style = ${textStyle("md.comp.navigation-drawer.label-text")}!;
5658
return style.apply(
57-
color: states.contains(MaterialState.selected)
59+
color: states.contains(MaterialState.disabled)
60+
? _colors.onSurfaceVariant.withOpacity(0.38)
61+
: states.contains(MaterialState.selected)
5862
? ${componentColor("md.comp.navigation-drawer.active.label-text")}
5963
: ${componentColor("md.comp.navigation-drawer.inactive.label-text")},
6064
);

examples/api/lib/material/card/card.2.dart

Lines changed: 16 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -16,97 +16,33 @@ class CardExamplesApp extends StatelessWidget {
1616
@override
1717
Widget build(BuildContext context) {
1818
return MaterialApp(
19-
theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),
2019
home: Scaffold(
2120
appBar: AppBar(title: const Text('Card Examples')),
22-
body: const Column(
23-
children: <Widget>[
24-
Spacer(),
25-
ElevatedCardExample(),
26-
FilledCardExample(),
27-
OutlinedCardExample(),
28-
Spacer(),
29-
],
30-
),
31-
),
32-
);
33-
}
34-
}
35-
36-
/// An example of the elevated card type.
37-
///
38-
/// The default settings for [Card] will provide an elevated
39-
/// card matching the spec:
40-
///
41-
/// https://m3.material.io/components/cards/specs#a012d40d-7a5c-4b07-8740-491dec79d58b
42-
class ElevatedCardExample extends StatelessWidget {
43-
const ElevatedCardExample({super.key});
44-
45-
@override
46-
Widget build(BuildContext context) {
47-
return const Center(
48-
child: Card(
49-
child: SizedBox(
50-
width: 300,
51-
height: 100,
52-
child: Center(child: Text('Elevated Card')),
53-
),
54-
),
55-
);
56-
}
57-
}
58-
59-
/// An example of the filled card type.
60-
///
61-
/// To make a [Card] match the filled type, the default elevation and color
62-
/// need to be changed to the values from the spec:
63-
///
64-
/// https://m3.material.io/components/cards/specs#0f55bf62-edf2-4619-b00d-b9ed462f2c5a
65-
class FilledCardExample extends StatelessWidget {
66-
const FilledCardExample({super.key});
67-
68-
@override
69-
Widget build(BuildContext context) {
70-
return Center(
71-
child: Card(
72-
elevation: 0,
73-
color: Theme.of(context).colorScheme.surfaceVariant,
74-
child: const SizedBox(
75-
width: 300,
76-
height: 100,
77-
child: Center(child: Text('Filled Card')),
21+
body: const Center(
22+
child: Column(
23+
mainAxisAlignment: MainAxisAlignment.center,
24+
children: <Widget>[
25+
Card(child: _SampleCard(cardName: 'Elevated Card')),
26+
Card.filled(child: _SampleCard(cardName: 'Filled Card')),
27+
Card.outlined(child: _SampleCard(cardName: 'Outlined Card')),
28+
],
29+
),
7830
),
7931
),
8032
);
8133
}
8234
}
8335

84-
/// An example of the outlined card type.
85-
///
86-
/// To make a [Card] match the outlined type, the default elevation and shape
87-
/// need to be changed to the values from the spec:
88-
///
89-
/// https://m3.material.io/components/cards/specs#0f55bf62-edf2-4619-b00d-b9ed462f2c5a
90-
class OutlinedCardExample extends StatelessWidget {
91-
const OutlinedCardExample({super.key});
36+
class _SampleCard extends StatelessWidget {
37+
const _SampleCard({required this.cardName});
38+
final String cardName;
9239

9340
@override
9441
Widget build(BuildContext context) {
95-
return Center(
96-
child: Card(
97-
elevation: 0,
98-
shape: RoundedRectangleBorder(
99-
side: BorderSide(
100-
color: Theme.of(context).colorScheme.outline,
101-
),
102-
borderRadius: const BorderRadius.all(Radius.circular(12)),
103-
),
104-
child: const SizedBox(
105-
width: 300,
106-
height: 100,
107-
child: Center(child: Text('Outlined Card')),
108-
),
109-
),
42+
return SizedBox(
43+
width: 300,
44+
height: 100,
45+
child: Center(child: Text(cardName)),
11046
);
11147
}
11248
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/material/card/card.2.dart' as example;
7+
import 'package:flutter_test/flutter_test.dart';
8+
9+
void main() {
10+
testWidgets('Card variants', (WidgetTester tester) async {
11+
await tester.pumpWidget(const example.CardExamplesApp());
12+
13+
expect(find.byType(Card), findsNWidgets(3));
14+
15+
expect(find.widgetWithText(Card, 'Elevated Card'), findsOneWidget);
16+
expect(find.widgetWithText(Card, 'Filled Card'), findsOneWidget);
17+
expect(find.widgetWithText(Card, 'Outlined Card'), findsOneWidget);
18+
19+
Material getCardMaterial(WidgetTester tester, int cardIndex) {
20+
return tester.widget<Material>(
21+
find.descendant(
22+
of: find.byType(Card).at(cardIndex),
23+
matching: find.byType(Material),
24+
),
25+
);
26+
}
27+
28+
final Material defaultCard = getCardMaterial(tester, 0);
29+
expect(defaultCard.clipBehavior, Clip.none);
30+
expect(defaultCard.elevation, 1.0);
31+
expect(defaultCard.shape, const RoundedRectangleBorder(
32+
borderRadius: BorderRadius.all(Radius.circular(12.0)),
33+
));
34+
expect(defaultCard.color, const Color(0xfffffbfe));
35+
expect(defaultCard.shadowColor, const Color(0xff000000));
36+
expect(defaultCard.surfaceTintColor, const Color(0xff6750a4));
37+
38+
final Material filledCard = getCardMaterial(tester, 1);
39+
expect(filledCard.clipBehavior, Clip.none);
40+
expect(filledCard.elevation, 0.0);
41+
expect(filledCard.shape, const RoundedRectangleBorder(
42+
borderRadius: BorderRadius.all(Radius.circular(12.0)),
43+
));
44+
expect(filledCard.color, const Color(0xffe7e0ec));
45+
expect(filledCard.shadowColor, const Color(0xff000000));
46+
expect(filledCard.surfaceTintColor, const Color(0x00000000));
47+
48+
final Material outlinedCard = getCardMaterial(tester, 2);
49+
expect(outlinedCard.clipBehavior, Clip.none);
50+
expect(outlinedCard.elevation, 0.0);
51+
expect(outlinedCard.shape, const RoundedRectangleBorder(
52+
side: BorderSide(color: Color(0xffcac4d0)),
53+
borderRadius: BorderRadius.all(Radius.circular(12.0)),
54+
));
55+
expect(outlinedCard.color, const Color(0xfffffbfe));
56+
expect(outlinedCard.shadowColor, const Color(0xff000000));
57+
expect(outlinedCard.surfaceTintColor, const Color(0xff6750a4));
58+
});
59+
}

0 commit comments

Comments
 (0)