Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit c14ca6d

Browse files
authored
Migrate common buttons to Material 3 (#100794)
1 parent 1755819 commit c14ca6d

File tree

14 files changed

+1113
-236
lines changed

14 files changed

+1113
-236
lines changed

dev/tools/gen_defaults/bin/gen_defaults.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import 'dart:convert';
1818
import 'dart:io';
1919

20+
import 'package:gen_defaults/button_template.dart';
2021
import 'package:gen_defaults/card_template.dart';
2122
import 'package:gen_defaults/dialog_template.dart';
2223
import 'package:gen_defaults/fab_template.dart';
@@ -77,6 +78,9 @@ Future<void> main(List<String> args) async {
7778
tokens['colorsLight'] = _readTokenFile('color_light.json');
7879
tokens['colorsDark'] = _readTokenFile('color_dark.json');
7980

81+
ButtonTemplate('md.comp.elevated-button', '$materialLib/elevated_button.dart', tokens).updateFile();
82+
ButtonTemplate('md.comp.outlined-button', '$materialLib/outlined_button.dart', tokens).updateFile();
83+
ButtonTemplate('md.comp.text-button', '$materialLib/text_button.dart', tokens).updateFile();
8084
CardTemplate('$materialLib/card.dart', tokens).updateFile();
8185
DialogTemplate('$materialLib/dialog.dart', tokens).updateFile();
8286
FABTemplate('$materialLib/floating_action_button.dart', tokens).updateFile();
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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 'template.dart';
6+
7+
class ButtonTemplate extends TokenTemplate {
8+
const ButtonTemplate(this.tokenGroup, String fileName, Map<String, dynamic> tokens)
9+
: super(fileName, tokens,
10+
colorSchemePrefix: '_colors.',
11+
);
12+
13+
final String tokenGroup;
14+
15+
String _backgroundColor() {
16+
if (tokens.containsKey('$tokenGroup.container.color')) {
17+
return '''
18+
19+
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
20+
if (states.contains(MaterialState.disabled))
21+
return ${componentColor('$tokenGroup.disabled.container')};
22+
return ${componentColor('$tokenGroup.container')};
23+
})''';
24+
}
25+
return '''
26+
27+
ButtonStyleButton.allOrNull<Color>(Colors.transparent)''';
28+
}
29+
30+
String _elevation() {
31+
if (tokens.containsKey('$tokenGroup.container.elevation')) {
32+
return '''
33+
34+
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
35+
if (states.contains(MaterialState.disabled))
36+
return ${elevation("$tokenGroup.disabled.container")};
37+
if (states.contains(MaterialState.hovered))
38+
return ${elevation("$tokenGroup.hover.container")};
39+
if (states.contains(MaterialState.focused))
40+
return ${elevation("$tokenGroup.focus.container")};
41+
if (states.contains(MaterialState.pressed))
42+
return ${elevation("$tokenGroup.pressed.container")};
43+
return ${elevation("$tokenGroup.container")};
44+
})''';
45+
}
46+
return '''
47+
48+
ButtonStyleButton.allOrNull<double>(0.0)''';
49+
}
50+
51+
@override
52+
String generate() => '''
53+
// Generated version ${tokens["version"]}
54+
class _TokenDefaultsM3 extends ButtonStyle {
55+
_TokenDefaultsM3(this.context)
56+
: super(
57+
animationDuration: kThemeChangeDuration,
58+
enableFeedback: true,
59+
alignment: Alignment.center,
60+
);
61+
62+
final BuildContext context;
63+
late final ColorScheme _colors = Theme.of(context).colorScheme;
64+
65+
@override
66+
MaterialStateProperty<TextStyle?> get textStyle =>
67+
MaterialStateProperty.all<TextStyle?>(${textStyle("$tokenGroup.label-text")});
68+
69+
@override
70+
MaterialStateProperty<Color?>? get backgroundColor =>${_backgroundColor()};
71+
72+
@override
73+
MaterialStateProperty<Color?>? get foregroundColor =>
74+
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
75+
if (states.contains(MaterialState.disabled))
76+
return ${componentColor('$tokenGroup.disabled.label-text')};
77+
return ${componentColor('$tokenGroup.label-text')};
78+
});
79+
80+
@override
81+
MaterialStateProperty<Color?>? get overlayColor =>
82+
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
83+
if (states.contains(MaterialState.hovered))
84+
return ${componentColor('$tokenGroup.hover.state-layer')};
85+
if (states.contains(MaterialState.focused))
86+
return ${componentColor('$tokenGroup.focus.state-layer')};
87+
if (states.contains(MaterialState.pressed))
88+
return ${componentColor('$tokenGroup.pressed.state-layer')};
89+
return null;
90+
});
91+
92+
${tokens.containsKey("$tokenGroup.container.shadow-color") ? '''
93+
@override
94+
MaterialStateProperty<Color>? get shadowColor =>
95+
ButtonStyleButton.allOrNull<Color>(${color("$tokenGroup.container.shadow-color")});''' : '''
96+
// No default shadow color'''}
97+
98+
${tokens.containsKey("$tokenGroup.container.surface-tint-layer.color") ? '''
99+
@override
100+
MaterialStateProperty<Color>? get surfaceTintColor =>
101+
ButtonStyleButton.allOrNull<Color>(${color("$tokenGroup.container.surface-tint-layer.color")});''' : '''
102+
// No default surface tint color'''}
103+
104+
@override
105+
MaterialStateProperty<double>? get elevation =>${_elevation()};
106+
107+
@override
108+
MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
109+
ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(_scaledPadding(context));
110+
111+
@override
112+
MaterialStateProperty<Size>? get minimumSize =>
113+
ButtonStyleButton.allOrNull<Size>(const Size(64.0, ${tokens["$tokenGroup.container.height"]}));
114+
115+
// No default fixedSize
116+
117+
@override
118+
MaterialStateProperty<Size>? get maximumSize =>
119+
ButtonStyleButton.allOrNull<Size>(Size.infinite);
120+
121+
${tokens.containsKey("$tokenGroup.outline.color") ? '''
122+
@override
123+
MaterialStateProperty<BorderSide>? get side =>
124+
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
125+
if (states.contains(MaterialState.disabled))
126+
return ${border("$tokenGroup.disabled.outline")};
127+
return ${border("$tokenGroup.outline")};
128+
});''' : '''
129+
// No default side'''}
130+
131+
@override
132+
MaterialStateProperty<OutlinedBorder>? get shape =>
133+
ButtonStyleButton.allOrNull<OutlinedBorder>(${shape("$tokenGroup.container")});
134+
135+
@override
136+
MaterialStateProperty<MouseCursor?>? get mouseCursor =>
137+
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
138+
if (states.contains(MaterialState.disabled))
139+
return SystemMouseCursors.basic;
140+
return SystemMouseCursors.click;
141+
});
142+
143+
@override
144+
VisualDensity? get visualDensity => Theme.of(context).visualDensity;
145+
146+
@override
147+
MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
148+
149+
@override
150+
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
151+
}
152+
''';
153+
}

dev/tools/gen_defaults/lib/template.dart

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ abstract class TokenTemplate {
7070
/// * [componentColor], that provides support for an optional opacity.
7171
String color(String colorToken) {
7272
return tokens.containsKey(colorToken)
73-
? '$colorSchemePrefix${tokens[colorToken]}'
74-
: 'null';
73+
? '$colorSchemePrefix${tokens[colorToken]}'
74+
: 'null';
7575
}
7676

7777
/// Generate a [ColorScheme] color name for the given component's color
@@ -91,17 +91,25 @@ abstract class TokenTemplate {
9191
if (!tokens.containsKey(colorToken))
9292
return 'null';
9393
String value = color(colorToken);
94-
final String tokenOpacity = '$componentToken.opacity';
95-
if (tokens.containsKey(tokenOpacity)) {
96-
final dynamic opacityValue = tokens[tokenOpacity];
97-
final String opacity = opacityValue is double
98-
? opacityValue.toString()
99-
: tokens[tokens[tokenOpacity]!]!.toString();
100-
value += '.withOpacity($opacity)';
94+
final String opacityToken = '$componentToken.opacity';
95+
if (tokens.containsKey(opacityToken)) {
96+
value += '.withOpacity(${opacity(opacityToken)})';
10197
}
10298
return value;
10399
}
104100

101+
/// Generate the opacity value for the given token.
102+
String? opacity(String token) {
103+
final dynamic value = tokens[token];
104+
if (value == null) {
105+
return null;
106+
}
107+
if (value is double) {
108+
return value.toString();
109+
}
110+
return tokens[value].toString();
111+
}
112+
105113
/// Generate an elevation value for the given component token.
106114
String elevation(String componentToken) {
107115
return tokens[tokens['$componentToken.elevation']!]!.toString();
@@ -135,7 +143,7 @@ abstract class TokenTemplate {
135143
return 'null';
136144
}
137145
final String borderColor = componentColor(componentToken);
138-
final double width = tokens['$componentToken.width'] as double;
146+
final double width = (tokens['$componentToken.width'] ?? 1.0) as double;
139147
return 'BorderSide(color: $borderColor${width != 1.0 ? ", width: $width" : ""})';
140148
}
141149

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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+
// Flutter code sample for ElevatedButton
6+
7+
import 'package:flutter/material.dart';
8+
9+
void main() {
10+
runApp(const ButtonApp());
11+
}
12+
13+
class ButtonApp extends StatelessWidget {
14+
const ButtonApp({Key? key}) : super(key: key);
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
return MaterialApp(
19+
theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),
20+
title: 'Button Types',
21+
home: const Scaffold(
22+
body: ButtonTypesExample(),
23+
),
24+
);
25+
}
26+
}
27+
28+
class ButtonTypesExample extends StatelessWidget {
29+
const ButtonTypesExample({Key? key}) : super(key: key);
30+
31+
@override
32+
Widget build(BuildContext context) {
33+
return Padding(
34+
padding: const EdgeInsets.all(4.0),
35+
child: Row(
36+
children: const <Widget>[
37+
Spacer(),
38+
ButtonTypesGroup(enabled: true),
39+
ButtonTypesGroup(enabled: false),
40+
Spacer(),
41+
],
42+
),
43+
);
44+
}
45+
}
46+
47+
class ButtonTypesGroup extends StatelessWidget {
48+
const ButtonTypesGroup({ Key? key, required this.enabled }) : super(key: key);
49+
50+
final bool enabled;
51+
52+
@override
53+
Widget build(BuildContext context) {
54+
final VoidCallback? onPressed = enabled ? () {} : null;
55+
return Padding(
56+
padding: const EdgeInsets.all(4.0),
57+
child: Column(
58+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
59+
children: <Widget>[
60+
ElevatedButton(onPressed: onPressed, child: const Text('Elevated')),
61+
62+
// Use an ElevatedButton with specific style to implement the
63+
// 'Filled' type.
64+
ElevatedButton(
65+
style: ElevatedButton.styleFrom(
66+
// Foreground color
67+
onPrimary: Theme.of(context).colorScheme.onPrimary,
68+
// Background color
69+
primary: Theme.of(context).colorScheme.primary,
70+
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
71+
onPressed: onPressed,
72+
child: const Text('Filled'),
73+
),
74+
75+
// Use an ElevatedButton with specific style to implement the
76+
// 'Filled Tonal' type.
77+
ElevatedButton(
78+
style: ElevatedButton.styleFrom(
79+
// Foreground color
80+
onPrimary: Theme.of(context).colorScheme.onSecondaryContainer,
81+
// Background color
82+
primary: Theme.of(context).colorScheme.secondaryContainer,
83+
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
84+
onPressed: onPressed,
85+
child: const Text('Filled Tonal'),
86+
),
87+
88+
OutlinedButton(onPressed: onPressed, child: const Text('Outlined')),
89+
90+
TextButton(onPressed: onPressed, child: const Text('Text')),
91+
],
92+
),
93+
);
94+
}
95+
}

0 commit comments

Comments
 (0)