Skip to content

Commit 091c102

Browse files
committed
button: Implement ZulipWebUiKitButton, to use for edit-message UI soon
1 parent 7e9fe4e commit 091c102

File tree

2 files changed

+172
-1
lines changed

2 files changed

+172
-1
lines changed

lib/widgets/button.dart

+123-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,126 @@
1-
import 'package:flutter/widgets.dart';
1+
import 'package:flutter/material.dart';
2+
3+
import 'color.dart';
4+
import 'text.dart';
5+
import 'theme.dart';
6+
7+
/// The "Button" component from Zulip Web UI kit.
8+
///
9+
/// The Figma uses this for the "Cancel" and "Save" buttons in the compose box
10+
/// for editing an already-sent message.
11+
///
12+
/// See Figma:
13+
/// * Component: https://www.figma.com/design/msWyAJ8cnMHgOMPxi7BUvA/Zulip-Web-UI-kit?node-id=1-2780&t=Wia0D0i1I0GXdD9z-0
14+
/// * Edit-message compose box: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3988-38201&m=dev
15+
class ZulipWebUiKitButton extends StatelessWidget {
16+
const ZulipWebUiKitButton({
17+
super.key,
18+
this.attention = ZulipWebUiKitButtonAttention.medium,
19+
this.intent = ZulipWebUiKitButtonIntent.info,
20+
required this.label,
21+
required this.onPressed,
22+
});
23+
24+
final ZulipWebUiKitButtonAttention attention;
25+
final ZulipWebUiKitButtonIntent intent;
26+
final String label;
27+
final VoidCallback onPressed;
28+
29+
WidgetStateColor _backgroundColor(DesignVariables designVariables) {
30+
switch ((attention, intent)) {
31+
case (ZulipWebUiKitButtonAttention.medium, ZulipWebUiKitButtonIntent.info):
32+
return WidgetStateColor.fromMap({
33+
WidgetState.pressed: designVariables.btnBgAttMediumIntInfoActive,
34+
~WidgetState.pressed: designVariables.btnBgAttMediumIntInfoNormal,
35+
});
36+
case (ZulipWebUiKitButtonAttention.high, ZulipWebUiKitButtonIntent.info):
37+
return WidgetStateColor.fromMap({
38+
WidgetState.pressed: designVariables.btnBgAttHighIntInfoActive,
39+
~WidgetState.pressed: designVariables.btnBgAttHighIntInfoNormal,
40+
});
41+
}
42+
}
43+
44+
Color _labelColor(DesignVariables designVariables) {
45+
switch ((attention, intent)) {
46+
case (ZulipWebUiKitButtonAttention.medium, ZulipWebUiKitButtonIntent.info):
47+
return designVariables.btnLabelAttMediumIntInfo;
48+
case (ZulipWebUiKitButtonAttention.high, ZulipWebUiKitButtonIntent.info):
49+
return designVariables.btnLabelAttHigh;
50+
}
51+
}
52+
53+
TextStyle _labelStyle(BuildContext context) {
54+
final designVariables = DesignVariables.of(context);
55+
return TextStyle(
56+
color: _labelColor(designVariables),
57+
fontSize: 17,
58+
height: 1.20,
59+
letterSpacing: proportionalLetterSpacing(context,
60+
0.006, baseFontSize: 17),
61+
).merge(weightVariableTextStyle(context, wght: 600));
62+
}
63+
64+
BorderSide _borderSide(DesignVariables designVariables) {
65+
switch (attention) {
66+
case ZulipWebUiKitButtonAttention.medium:
67+
// TODO inner shadow effect like `box-shadow: inset`, following Figma;
68+
// needs Flutter support for something like that:
69+
// https://github.com/flutter/flutter/issues/18636
70+
// https://github.com/flutter/flutter/issues/52999
71+
// For now, we just use a solid-stroke border with half the opacity
72+
// and half the width.
73+
return BorderSide(
74+
color: designVariables.btnShadowAttMed.withFadedAlpha(0.5),
75+
width: 0.5);
76+
case ZulipWebUiKitButtonAttention.high:
77+
return BorderSide.none;
78+
}
79+
}
80+
81+
@override
82+
Widget build(BuildContext context) {
83+
final designVariables = DesignVariables.of(context);
84+
85+
return AnimatedScaleOnTap(
86+
scaleEnd: 0.96,
87+
duration: Duration(milliseconds: 100),
88+
child: TextButton(
89+
style: TextButton.styleFrom(
90+
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
91+
foregroundColor: _labelColor(designVariables),
92+
shape: RoundedRectangleBorder(
93+
side: _borderSide(designVariables),
94+
borderRadius: BorderRadius.circular(4)),
95+
splashFactory: NoSplash.splashFactory,
96+
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
97+
minimumSize: Size(kMinInteractiveDimension, 28),
98+
).copyWith(backgroundColor: _backgroundColor(designVariables)),
99+
onPressed: onPressed,
100+
child: ConstrainedBox(
101+
constraints: BoxConstraints(maxWidth: 240),
102+
child: Text(label,
103+
maxLines: 1,
104+
style: _labelStyle(context),
105+
textAlign: TextAlign.center,
106+
overflow: TextOverflow.ellipsis))));
107+
}
108+
}
109+
110+
enum ZulipWebUiKitButtonAttention {
111+
high,
112+
medium,
113+
// low,
114+
}
115+
116+
enum ZulipWebUiKitButtonIntent {
117+
// neutral,
118+
// warning,
119+
// danger,
120+
info,
121+
// success,
122+
// brand,
123+
}
2124

3125
/// Apply [Transform.scale] to the child widget when tapped, and reset its scale
4126
/// when released, while animating the transitions.

lib/widgets/theme.dart

+49
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,15 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
138138
bgTopBar: const Color(0xfff5f5f5),
139139
borderBar: Colors.black.withValues(alpha: 0.2),
140140
borderMenuButtonSelected: Colors.black.withValues(alpha: 0.2),
141+
btnBgAttHighIntInfoActive: const Color(0xff1e41d3),
142+
btnBgAttHighIntInfoNormal: const Color(0xff3c6bff),
143+
btnBgAttMediumIntInfoActive: const Color(0xff3c6bff).withValues(alpha: 0.22),
144+
btnBgAttMediumIntInfoNormal: const Color(0xff3c6bff).withValues(alpha: 0.12),
145+
btnLabelAttHigh: const Color(0xffffffff),
141146
btnLabelAttLowIntDanger: const Color(0xffc0070a),
142147
btnLabelAttMediumIntDanger: const Color(0xffac0508),
148+
btnLabelAttMediumIntInfo: const Color(0xff1027a6),
149+
btnShadowAttMed: const Color(0xff000000).withValues(alpha: 0.20),
143150
composeBoxBg: const Color(0xffffffff),
144151
contextMenuCancelText: const Color(0xff222222),
145152
contextMenuItemBg: const Color(0xff6159e1),
@@ -188,8 +195,15 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
188195
bgTopBar: const Color(0xff242424),
189196
borderBar: const Color(0xffffffff).withValues(alpha: 0.1),
190197
borderMenuButtonSelected: Colors.white.withValues(alpha: 0.1),
198+
btnBgAttHighIntInfoActive: const Color(0xff1e41d3),
199+
btnBgAttHighIntInfoNormal: const Color(0xff1e41d3),
200+
btnBgAttMediumIntInfoActive: const Color(0xff97b6fe).withValues(alpha: 0.12),
201+
btnBgAttMediumIntInfoNormal: const Color(0xff97b6fe).withValues(alpha: 0.12),
202+
btnLabelAttHigh: const Color(0xffffffff).withValues(alpha: 0.85),
191203
btnLabelAttLowIntDanger: const Color(0xffff8b7c),
192204
btnLabelAttMediumIntDanger: const Color(0xffff8b7c),
205+
btnLabelAttMediumIntInfo: const Color(0xff97b6fe),
206+
btnShadowAttMed: const Color(0xffffffff).withValues(alpha: 0.21),
193207
composeBoxBg: const Color(0xff0f0f0f),
194208
contextMenuCancelText: const Color(0xffffffff).withValues(alpha: 0.75),
195209
contextMenuItemBg: const Color(0xff7977fe),
@@ -246,8 +260,15 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
246260
required this.bgTopBar,
247261
required this.borderBar,
248262
required this.borderMenuButtonSelected,
263+
required this.btnBgAttHighIntInfoActive,
264+
required this.btnBgAttHighIntInfoNormal,
265+
required this.btnBgAttMediumIntInfoActive,
266+
required this.btnBgAttMediumIntInfoNormal,
267+
required this.btnLabelAttHigh,
249268
required this.btnLabelAttLowIntDanger,
250269
required this.btnLabelAttMediumIntDanger,
270+
required this.btnLabelAttMediumIntInfo,
271+
required this.btnShadowAttMed,
251272
required this.composeBoxBg,
252273
required this.contextMenuCancelText,
253274
required this.contextMenuItemBg,
@@ -305,8 +326,15 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
305326
final Color bgTopBar;
306327
final Color borderBar;
307328
final Color borderMenuButtonSelected;
329+
final Color btnBgAttHighIntInfoActive;
330+
final Color btnBgAttHighIntInfoNormal;
331+
final Color btnBgAttMediumIntInfoActive;
332+
final Color btnBgAttMediumIntInfoNormal;
333+
final Color btnLabelAttHigh;
308334
final Color btnLabelAttLowIntDanger;
309335
final Color btnLabelAttMediumIntDanger;
336+
final Color btnLabelAttMediumIntInfo;
337+
final Color btnShadowAttMed;
310338
final Color composeBoxBg;
311339
final Color contextMenuCancelText;
312340
final Color contextMenuItemBg;
@@ -359,8 +387,15 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
359387
Color? bgTopBar,
360388
Color? borderBar,
361389
Color? borderMenuButtonSelected,
390+
Color? btnBgAttHighIntInfoActive,
391+
Color? btnBgAttHighIntInfoNormal,
392+
Color? btnBgAttMediumIntInfoActive,
393+
Color? btnBgAttMediumIntInfoNormal,
394+
Color? btnLabelAttHigh,
362395
Color? btnLabelAttLowIntDanger,
363396
Color? btnLabelAttMediumIntDanger,
397+
Color? btnLabelAttMediumIntInfo,
398+
Color? btnShadowAttMed,
364399
Color? composeBoxBg,
365400
Color? contextMenuCancelText,
366401
Color? contextMenuItemBg,
@@ -408,8 +443,15 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
408443
bgTopBar: bgTopBar ?? this.bgTopBar,
409444
borderBar: borderBar ?? this.borderBar,
410445
borderMenuButtonSelected: borderMenuButtonSelected ?? this.borderMenuButtonSelected,
446+
btnBgAttHighIntInfoActive: btnBgAttHighIntInfoActive ?? this.btnBgAttHighIntInfoActive,
447+
btnBgAttHighIntInfoNormal: btnBgAttHighIntInfoNormal ?? this.btnBgAttHighIntInfoNormal,
448+
btnBgAttMediumIntInfoActive: btnBgAttMediumIntInfoActive ?? this.btnBgAttMediumIntInfoActive,
449+
btnBgAttMediumIntInfoNormal: btnBgAttMediumIntInfoNormal ?? this.btnBgAttMediumIntInfoNormal,
450+
btnLabelAttHigh: btnLabelAttHigh ?? this.btnLabelAttHigh,
411451
btnLabelAttLowIntDanger: btnLabelAttLowIntDanger ?? this.btnLabelAttLowIntDanger,
412452
btnLabelAttMediumIntDanger: btnLabelAttMediumIntDanger ?? this.btnLabelAttMediumIntDanger,
453+
btnLabelAttMediumIntInfo: btnLabelAttMediumIntInfo ?? this.btnLabelAttMediumIntInfo,
454+
btnShadowAttMed: btnShadowAttMed ?? this.btnShadowAttMed,
413455
composeBoxBg: composeBoxBg ?? this.composeBoxBg,
414456
contextMenuCancelText: contextMenuCancelText ?? this.contextMenuCancelText,
415457
contextMenuItemBg: contextMenuItemBg ?? this.contextMenuItemBg,
@@ -464,8 +506,15 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
464506
bgTopBar: Color.lerp(bgTopBar, other.bgTopBar, t)!,
465507
borderBar: Color.lerp(borderBar, other.borderBar, t)!,
466508
borderMenuButtonSelected: Color.lerp(borderMenuButtonSelected, other.borderMenuButtonSelected, t)!,
509+
btnBgAttHighIntInfoActive: Color.lerp(btnBgAttHighIntInfoActive, other.btnBgAttHighIntInfoActive, t)!,
510+
btnBgAttHighIntInfoNormal: Color.lerp(btnBgAttHighIntInfoNormal, other.btnBgAttHighIntInfoNormal, t)!,
511+
btnBgAttMediumIntInfoActive: Color.lerp(btnBgAttMediumIntInfoActive, other.btnBgAttMediumIntInfoActive, t)!,
512+
btnBgAttMediumIntInfoNormal: Color.lerp(btnBgAttMediumIntInfoNormal, other.btnBgAttMediumIntInfoNormal, t)!,
513+
btnLabelAttHigh: Color.lerp(btnLabelAttHigh, other.btnLabelAttHigh, t)!,
467514
btnLabelAttLowIntDanger: Color.lerp(btnLabelAttLowIntDanger, other.btnLabelAttLowIntDanger, t)!,
468515
btnLabelAttMediumIntDanger: Color.lerp(btnLabelAttMediumIntDanger, other.btnLabelAttMediumIntDanger, t)!,
516+
btnLabelAttMediumIntInfo: Color.lerp(btnLabelAttMediumIntInfo, other.btnLabelAttMediumIntInfo, t)!,
517+
btnShadowAttMed: Color.lerp(btnShadowAttMed, other.btnShadowAttMed, t)!,
469518
composeBoxBg: Color.lerp(composeBoxBg, other.composeBoxBg, t)!,
470519
contextMenuCancelText: Color.lerp(contextMenuCancelText, other.contextMenuCancelText, t)!,
471520
contextMenuItemBg: Color.lerp(contextMenuItemBg, other.contextMenuItemBg, t)!,

0 commit comments

Comments
 (0)