@@ -257,3 +257,75 @@ class _AnimatedScaleOnTapState extends State<AnimatedScaleOnTap> {
257
257
child: widget.child));
258
258
}
259
259
}
260
+
261
+ /// The rounded-rectangle shape and 1-pixel spacing for a run of [MenuButton] s.
262
+ class MenuButtonsShape extends StatelessWidget {
263
+ const MenuButtonsShape ({
264
+ super .key,
265
+ required this .buttons,
266
+ });
267
+
268
+ final List <Widget > buttons;
269
+
270
+ @override
271
+ Widget build (BuildContext context) {
272
+ return ClipRRect (
273
+ borderRadius: BorderRadius .circular (7 ),
274
+ child: Column (spacing: 1 ,
275
+ children: buttons));
276
+ }
277
+ }
278
+
279
+ /// The "menu button" component in Figma.
280
+ ///
281
+ /// Must have a [MenuButtonsShape] ancestor.
282
+ ///
283
+ /// See Figma:
284
+ /// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=6070-60681&m=dev
285
+ class MenuButton extends StatelessWidget {
286
+ const MenuButton ({
287
+ super .key,
288
+ required this .label,
289
+ required this .onPressed,
290
+ this .icon,
291
+ });
292
+
293
+ final String label;
294
+ final VoidCallback onPressed;
295
+ final IconData ? icon;
296
+
297
+ static bool _debugCheckShapeAncestor (BuildContext context) {
298
+ final ancestor = context.findAncestorWidgetOfExactType <MenuButtonsShape >();
299
+ assert (() {
300
+ if (ancestor != null ) return true ;
301
+ throw FlutterError .fromParts ([
302
+ ErrorSummary ('No MenuButtonsShape ancestor found.' ),
303
+ ErrorDescription ('MenuButton widgets require a MenuButtonsShape ancestor.' ),
304
+ ]);
305
+ }());
306
+ return true ;
307
+ }
308
+
309
+ @override
310
+ Widget build (BuildContext context) {
311
+ _debugCheckShapeAncestor (context);
312
+
313
+ final designVariables = DesignVariables .of (context);
314
+
315
+ return MenuItemButton (
316
+ trailingIcon: icon != null
317
+ ? Icon (icon, color: designVariables.contextMenuItemText)
318
+ : null ,
319
+ style: MenuItemButton .styleFrom (
320
+ padding: const EdgeInsets .symmetric (vertical: 12 , horizontal: 16 ),
321
+ foregroundColor: designVariables.contextMenuItemText,
322
+ splashFactory: NoSplash .splashFactory,
323
+ ).copyWith (backgroundColor: WidgetStateColor .resolveWith ((states) =>
324
+ designVariables.contextMenuItemBg.withFadedAlpha (
325
+ states.contains (WidgetState .pressed) ? 0.20 : 0.12 ))),
326
+ onPressed: onPressed,
327
+ child: Text (label,
328
+ style: const TextStyle (fontSize: 20 , height: 24 / 20 )
329
+ .merge (weightVariableTextStyle (context, wght: 600 ))));
330
+ }
331
+ }
0 commit comments