Skip to content

Commit 28455d8

Browse files
committed
button [nfc]: Add "list button" variant, factored into MenuButton widget
And rename MenuButton to ZulipMenuItemButton, since it's no longer only about the Figma's "menu button" component.
1 parent d5da314 commit 28455d8

File tree

5 files changed

+98
-19
lines changed

5 files changed

+98
-19
lines changed

lib/widgets/action_sheet.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ abstract class ActionSheetMenuItemButton extends StatelessWidget {
152152
@override
153153
Widget build(BuildContext context) {
154154
final zulipLocalizations = ZulipLocalizations.of(context);
155-
return MenuButton(
155+
return ZulipMenuItemButton(
156156
icon: icon,
157157
label: label(zulipLocalizations),
158158
onPressed: () => _handlePressed(context),

lib/widgets/button.dart

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ class _AnimatedScaleOnTapState extends State<AnimatedScaleOnTap> {
259259
}
260260
}
261261

262-
/// The rounded-rectangle shape and 1-pixel spacing for a run of [MenuButton]s.
262+
/// The rounded-rectangle shape and 1-pixel spacing for a run of [ZulipMenuItemButton]s.
263263
class MenuButtonsShape extends StatelessWidget {
264264
const MenuButtonsShape({
265265
super.key,
@@ -277,21 +277,25 @@ class MenuButtonsShape extends StatelessWidget {
277277
}
278278
}
279279

280-
/// The "menu button" component in Figma.
280+
/// The "menu button" or "list button" component in Figma.
281+
///
282+
/// Use [ZulipMenuItemButtonStyle] to choose between components.
281283
///
282284
/// Must have a [MenuButtonsShape] ancestor.
283285
///
284286
/// See Figma:
285287
/// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=6070-60681&m=dev
286-
class MenuButton extends StatelessWidget {
287-
const MenuButton({
288+
class ZulipMenuItemButton extends StatelessWidget {
289+
const ZulipMenuItemButton({
288290
super.key,
291+
this.style = ZulipMenuItemButtonStyle.menu,
289292
required this.label,
290293
required this.onPressed,
291294
this.icon,
292295
this.toggle,
293296
});
294297

298+
final ZulipMenuItemButtonStyle style;
295299
final String label;
296300
final VoidCallback onPressed;
297301
final IconData? icon;
@@ -300,22 +304,63 @@ class MenuButton extends StatelessWidget {
300304
///
301305
/// See Figma:
302306
/// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=6070-60682&m=dev
307+
// TODO(design) Is the toggle option meant only for
308+
// [ZulipMenuItemButtonStyle.menu]?
303309
final Widget? toggle;
304310

305-
static double itemSpacing = 16;
311+
double get itemSpacingAndEndPadding => switch (style) {
312+
ZulipMenuItemButtonStyle.menu => 16,
313+
ZulipMenuItemButtonStyle.list => 12,
314+
};
306315

307316
static bool _debugCheckShapeAncestor(BuildContext context) {
308317
final ancestor = context.findAncestorWidgetOfExactType<MenuButtonsShape>();
309318
assert(() {
310319
if (ancestor != null) return true;
311320
throw FlutterError.fromParts([
312321
ErrorSummary('No MenuButtonsShape ancestor found.'),
313-
ErrorDescription('MenuButton widgets require a MenuButtonsShape ancestor.'),
322+
ErrorDescription('ZulipMenuItemButton widgets require a MenuButtonsShape ancestor.'),
314323
]);
315324
}());
316325
return true;
317326
}
318327

328+
WidgetStateColor _backgroundColor(DesignVariables designVariables) {
329+
switch (style) {
330+
case ZulipMenuItemButtonStyle.menu:
331+
return WidgetStateColor.fromMap({
332+
WidgetState.pressed: designVariables.contextMenuItemBg.withFadedAlpha(0.20),
333+
~WidgetState.pressed: designVariables.contextMenuItemBg.withFadedAlpha(0.12),
334+
});
335+
case ZulipMenuItemButtonStyle.list:
336+
return WidgetStateColor.fromMap({
337+
WidgetState.pressed: designVariables.listMenuItemBg.withFadedAlpha(0.7),
338+
~WidgetState.pressed: designVariables.listMenuItemBg.withFadedAlpha(0.35),
339+
});
340+
}
341+
}
342+
343+
Color _labelColor(DesignVariables designVariables) {
344+
return switch (style) {
345+
ZulipMenuItemButtonStyle.menu => designVariables.contextMenuItemText,
346+
ZulipMenuItemButtonStyle.list => designVariables.listMenuItemText,
347+
};
348+
}
349+
350+
double _labelWght() {
351+
return switch (style) {
352+
ZulipMenuItemButtonStyle.menu => 600,
353+
ZulipMenuItemButtonStyle.list => 500,
354+
};
355+
}
356+
357+
Color _iconColor(DesignVariables designVariables) {
358+
return switch (style) {
359+
ZulipMenuItemButtonStyle.menu => designVariables.contextMenuItemIcon,
360+
ZulipMenuItemButtonStyle.list => designVariables.listMenuItemIcon,
361+
};
362+
}
363+
319364
@override
320365
Widget build(BuildContext context) {
321366
_debugCheckShapeAncestor(context);
@@ -331,35 +376,48 @@ class MenuButton extends StatelessWidget {
331376
// This Material widget gives us 12px padding before the icon --
332377
// or more or less, depending on Theme.of(context).visualDensity,
333378
// hence the `assert` above.
334-
padding: EdgeInsetsDirectional.only(start: itemSpacing - 12),
379+
padding: EdgeInsetsDirectional.only(start: itemSpacingAndEndPadding - 12),
335380

336381
child: Row(
337382
mainAxisSize: MainAxisSize.min,
338383
crossAxisAlignment: CrossAxisAlignment.center,
339-
spacing: itemSpacing,
384+
spacing: itemSpacingAndEndPadding,
340385
children: [
341386
if (toggle != null) toggle!,
342-
if (icon != null) Icon(icon!, color: designVariables.contextMenuItemIcon),
387+
if (icon != null) Icon(icon!, color: _iconColor(designVariables)),
343388
]))
344389
: null,
345390
style: MenuItemButton.styleFrom(
346391
minimumSize: Size.fromHeight(48),
347-
padding: const EdgeInsets.symmetric(horizontal: 16),
348-
foregroundColor: designVariables.contextMenuItemText,
392+
padding: EdgeInsetsDirectional.only(start: 16, end: itemSpacingAndEndPadding),
393+
foregroundColor: _labelColor(designVariables),
349394
splashFactory: NoSplash.splashFactory,
350-
).copyWith(backgroundColor: WidgetStateColor.resolveWith((states) =>
351-
designVariables.contextMenuItemBg.withFadedAlpha(
352-
states.contains(WidgetState.pressed) ? 0.20 : 0.12))),
395+
).copyWith(backgroundColor: _backgroundColor(designVariables)),
353396
onPressed: onPressed,
354397
child: Padding(
355398
padding: const EdgeInsets.symmetric(vertical: 4),
399+
// TODO sublabel, for [ZulipMenuItemButtonStyle.list]
356400
child: Text(label,
357401
style: const TextStyle(fontSize: 20, height: 24 / 20)
358-
.merge(weightVariableTextStyle(context, wght: 600))),
359-
));
402+
.merge(weightVariableTextStyle(context, wght: _labelWght())))));
360403
}
361404
}
362405

406+
/// The style of a [ZulipMenuItemButton].
407+
enum ZulipMenuItemButtonStyle {
408+
/// The purple "menu button" component in Figma, with 16px end padding.
409+
///
410+
/// See Figma:
411+
/// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3302-20443&m=dev
412+
menu,
413+
414+
/// The gray "list button" component in Figma, with 12px end padding.
415+
///
416+
/// See Figma:
417+
/// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=5000-52868&m=dev
418+
list,
419+
}
420+
363421
/// The "toggle" component in Figma.
364422
///
365423
/// See Figma:

lib/widgets/profile.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ class _InvisibleModeToggle extends StatelessWidget {
139139
requestedValue
140140
? zulipLocalizations.turnOnInvisibleModeErrorTitle
141141
: zulipLocalizations.turnOffInvisibleModeErrorTitle),
142-
builder: (value, handleRequestNewValue) => MenuButton(
142+
builder: (value, handleRequestNewValue) => ZulipMenuItemButton(
143143
label: zulipLocalizations.invisibleMode,
144144
onPressed: () => handleRequestNewValue(!value),
145145
toggle: Toggle(value: value, onChanged: handleRequestNewValue))),

lib/widgets/theme.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
171171
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 0).toColor(),
172172
labelMenuButton: const Color(0xff222222),
173173
labelSearchPrompt: const Color(0xff000000).withValues(alpha: 0.5),
174+
listMenuItemBg: const Color(0xffcbcdd6),
175+
listMenuItemIcon: const Color(0xff9194a3),
176+
listMenuItemText: const Color(0xff2d303c),
174177
mainBackground: const Color(0xfff0f0f0),
175178
neutralButtonBg: const Color(0xff8c84ae),
176179
neutralButtonLabel: const Color(0xff433d5c),
@@ -251,6 +254,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
251254
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 1).toColor(),
252255
labelMenuButton: const Color(0xffffffff).withValues(alpha: 0.85),
253256
labelSearchPrompt: const Color(0xffffffff).withValues(alpha: 0.5),
257+
listMenuItemBg: const Color(0xff2d303c),
258+
listMenuItemIcon: const Color(0xff767988),
259+
listMenuItemText: const Color(0xffcbcdd6),
254260
mainBackground: const Color(0xff1d1d1d),
255261
neutralButtonBg: const Color(0xffd4d1e0),
256262
neutralButtonLabel: const Color(0xffa9a3c2),
@@ -339,6 +345,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
339345
required this.labelEdited,
340346
required this.labelMenuButton,
341347
required this.labelSearchPrompt,
348+
required this.listMenuItemBg,
349+
required this.listMenuItemIcon,
350+
required this.listMenuItemText,
342351
required this.mainBackground,
343352
required this.neutralButtonBg,
344353
required this.neutralButtonLabel,
@@ -424,6 +433,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
424433
final Color labelEdited;
425434
final Color labelMenuButton;
426435
final Color labelSearchPrompt;
436+
final Color listMenuItemBg;
437+
final Color listMenuItemIcon;
438+
final Color listMenuItemText;
427439
final Color mainBackground;
428440
final Color neutralButtonBg;
429441
final Color neutralButtonLabel;
@@ -504,6 +516,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
504516
Color? labelEdited,
505517
Color? labelMenuButton,
506518
Color? labelSearchPrompt,
519+
Color? listMenuItemBg,
520+
Color? listMenuItemIcon,
521+
Color? listMenuItemText,
507522
Color? mainBackground,
508523
Color? neutralButtonBg,
509524
Color? neutralButtonLabel,
@@ -579,6 +594,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
579594
labelEdited: labelEdited ?? this.labelEdited,
580595
labelMenuButton: labelMenuButton ?? this.labelMenuButton,
581596
labelSearchPrompt: labelSearchPrompt ?? this.labelSearchPrompt,
597+
listMenuItemBg: listMenuItemBg ?? this.listMenuItemBg,
598+
listMenuItemIcon: listMenuItemIcon ?? this.listMenuItemIcon,
599+
listMenuItemText: listMenuItemText ?? this.listMenuItemText,
582600
mainBackground: mainBackground ?? this.mainBackground,
583601
neutralButtonBg: neutralButtonBg ?? this.neutralButtonBg,
584602
neutralButtonLabel: neutralButtonLabel ?? this.neutralButtonLabel,
@@ -661,6 +679,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
661679
labelEdited: Color.lerp(labelEdited, other.labelEdited, t)!,
662680
labelMenuButton: Color.lerp(labelMenuButton, other.labelMenuButton, t)!,
663681
labelSearchPrompt: Color.lerp(labelSearchPrompt, other.labelSearchPrompt, t)!,
682+
listMenuItemBg: Color.lerp(listMenuItemBg, other.listMenuItemBg, t)!,
683+
listMenuItemIcon: Color.lerp(listMenuItemIcon, other.listMenuItemIcon, t)!,
684+
listMenuItemText: Color.lerp(listMenuItemText, other.listMenuItemText, t)!,
664685
mainBackground: Color.lerp(mainBackground, other.mainBackground, t)!,
665686
neutralButtonBg: Color.lerp(neutralButtonBg, other.neutralButtonBg, t)!,
666687
neutralButtonLabel: Color.lerp(neutralButtonLabel, other.neutralButtonLabel, t)!,

test/widgets/profile_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ void main() {
379379
});
380380

381381
group('invisible mode', () {
382-
final findRow = find.widgetWithText(MenuButton, 'Invisible mode');
382+
final findRow = find.widgetWithText(ZulipMenuItemButton, 'Invisible mode');
383383
final findToggle = find.descendant(of: findRow, matching: find.byType(Toggle));
384384

385385
void checkDoesNotAppear(WidgetTester tester) {

0 commit comments

Comments
 (0)