Skip to content

Commit

Permalink
feat(UX-1342): Added popup menu to ZdsComment (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikecoomber authored Dec 13, 2024
1 parent ae608cf commit f153842
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 65 deletions.
23 changes: 23 additions & 0 deletions example/lib/pages/components/comment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ class _CommentDemoState extends State<CommentDemo> {
),
author: 'John Doe',
downloadCallback: () {},
onMenuItemSelected: (val) {
print(val);
},
menuItems: [
ZdsPopupMenuItem(
value: 1,
child: Row(
children: [
Icon(ZdsIcons.delete),
Text('Delete'),
],
),
),
ZdsPopupMenuItem(
value: 2,
child: Row(
children: [
Icon(ZetaIcons.reply),
Text('Reply'),
],
),
),
],
comment: 'This is a comment',
onReply: () {
print('reply');
Expand Down
158 changes: 95 additions & 63 deletions lib/src/components/molecules/comment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class ZdsComment extends StatelessWidget {
this.deleteSemanticLabel,
this.replySemanticLabel,
this.attachmentThumbnail,
this.menuItems,
this.onMenuItemSelected,
}) : assert(
onReply != null && replySemanticLabel != null || onReply == null && replySemanticLabel == null,
'replySemanticLabel must be not null if onReply is defined',
Expand Down Expand Up @@ -72,6 +74,14 @@ class ZdsComment extends StatelessWidget {
/// The custom thumbnail to display for the attachment.
final Widget? attachmentThumbnail;

/// The menu items to display in the popup menu.
/// If defined, the pouup menu will be shown when the user taps on the comment.
final List<ZdsPopupMenuItem<int>>? menuItems;

/// The callback to be called when a menu item is selected.
/// Menu items must be given a value for the callback to trigger.
final ValueChanged<int>? onMenuItemSelected;

@override
Widget build(BuildContext context) {
final colors = Zeta.of(context).colors;
Expand Down Expand Up @@ -117,73 +127,94 @@ class ZdsComment extends StatelessWidget {
foregroundColor: colors.error,
),
],
child: Container(
decoration: BoxDecoration(
color: colors.surfaceDefault,
border: Border(
bottom: BorderSide(
color: colors.borderSubtle,
child: Builder(
builder: (context) {
final child = Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: colors.borderSubtle,
),
),
),
),
),
padding: EdgeInsets.symmetric(
vertical: spacing.large,
horizontal: spacing.medium,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: spacing.minimum),
child: Row(
children: [
if (avatar != null)
Padding(
padding: EdgeInsets.only(right: spacing.small),
child: avatar,
padding: EdgeInsets.symmetric(
vertical: spacing.large,
horizontal: spacing.medium,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: spacing.minimum),
child: Row(
children: [
if (avatar != null)
Padding(
padding: EdgeInsets.only(right: spacing.small),
child: avatar,
),
if (author != null)
Text(
author!,
style: ZetaTextStyles.labelLarge.copyWith(
fontWeight: FontWeight.w500,
),
),
const Spacer(),
if (timeStamp != null)
Padding(
padding: EdgeInsets.only(left: spacing.small),
child: Text(
timeStamp!,
style: ZetaTextStyles.bodyXSmall.copyWith(color: colors.textSubtle),
),
),
],
),
),
if (comment != null)
Padding(
padding: EdgeInsets.only(
top: spacing.small,
left: spacing.minimum,
right: spacing.minimum,
),
if (author != null)
Text(
author!,
style: ZetaTextStyles.labelLarge.copyWith(
fontWeight: FontWeight.w500,
),
child: Text(
comment!,
style: Theme.of(context).textTheme.bodyMedium,
),
const Spacer(),
if (timeStamp != null)
Padding(
padding: EdgeInsets.only(left: spacing.small),
child: Text(
timeStamp!,
style: ZetaTextStyles.bodyXSmall.copyWith(color: colors.textSubtle),
),
),
if (attachment != null)
Padding(
padding: EdgeInsets.only(top: spacing.medium),
child: _AttachmentRow(
attachment: attachment!,
downloadCallback: downloadCallback,
customThumbnail: attachmentThumbnail,
),
],
),
),
],
),
if (comment != null)
Padding(
padding: EdgeInsets.only(
top: spacing.small,
left: spacing.minimum,
right: spacing.minimum,
),
child: Text(
comment!,
style: Theme.of(context).textTheme.bodyMedium,
),
),
if (attachment != null)
Padding(
padding: EdgeInsets.only(top: spacing.medium),
child: _AttachmentRow(
attachment: attachment!,
downloadCallback: downloadCallback,
customThumbnail: attachmentThumbnail,
),
),
],
),
);
if (menuItems != null) {
return ZdsPopupMenu<int>(
menuPosition: ZdsPopupMenuPosition.topRight,
verticalOffset: spacing.small,
items: menuItems ?? [],
onSelected: onMenuItemSelected,
builder: (context, open) {
return Material(
color: colors.surfaceDefault,
child: InkWell(
onTap: open,
child: child,
),
);
},
);
}
return ColoredBox(color: colors.surfaceDefault, child: child);
},
),
);
},
Expand All @@ -206,7 +237,8 @@ class ZdsComment extends StatelessWidget {
..add(DiagnosticsProperty<ZdsChatAttachment?>('attachment', attachment))
..add(ObjectFlagProperty<VoidCallback?>.has('downloadCallback', downloadCallback))
..add(StringProperty('deleteSemanticLabel', deleteSemanticLabel))
..add(StringProperty('replySemanticLabel', replySemanticLabel));
..add(StringProperty('replySemanticLabel', replySemanticLabel))
..add(ObjectFlagProperty<ValueChanged<int>?>.has('onMenuItemSelected', onMenuItemSelected));
}
}

Expand Down
52 changes: 50 additions & 2 deletions lib/src/components/molecules/menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../../zds_flutter.dart';

/// Defines the position of a [ZdsPopupMenu].
enum ZdsPopupMenuPosition {
/// The menu will appear at the top left of the button.
topLeft,

/// The menu will appear at the top right of the button.
topRight,

/// The menu will appear at the bottom left below the button.
bottomLeft,

/// The menu will appear at the bottom right below the button.
bottomRight,
}

/// Creates a popup menu.
///
/// This component is typically used to display more options that do not fit in a [ZdsAppBar], or to show more
Expand Down Expand Up @@ -34,15 +49,19 @@ import '../../../../zds_flutter.dart';
/// See also:
///
/// * [ZdsPopupMenuItem], used to create the options that appear in this menu.
/// * [ZdsPopupMenuPosition], defines the position of a menu.
/// * [ZdsAppBar], where this component is used to show more actions that do not typically fit.
class ZdsPopupMenu<T> extends StatefulWidget {
/// Creates a pop up menu.
const ZdsPopupMenu({
required this.builder,
required this.items,
this.menuPosition = ZdsPopupMenuPosition.bottomLeft,
super.key,
this.onCanceled,
this.onSelected,
this.verticalOffset = 0,
this.horizontalOffset = 0,
}) : assert(items.length > 0, 'Must have at least 1 item');

/// Defines how this component will appear on screen.
Expand All @@ -60,6 +79,15 @@ class ZdsPopupMenu<T> extends StatefulWidget {
/// A function called whenever an item is selected.
final PopupMenuItemSelected<T>? onSelected;

/// The position of the menu.
final ZdsPopupMenuPosition menuPosition;

/// The vertical offset of the menu.
final double verticalOffset;

/// The horizontal offset of the menu.
final double horizontalOffset;

/// A function called whenever the user doesn't select an item and instead closes the menu.
final PopupMenuCanceled? onCanceled;

Expand All @@ -71,7 +99,10 @@ class ZdsPopupMenu<T> extends StatefulWidget {
properties
..add(ObjectFlagProperty<Widget Function(BuildContext p1, VoidCallback p2)>.has('builder', builder))
..add(ObjectFlagProperty<PopupMenuItemSelected<T>?>.has('onSelected', onSelected))
..add(ObjectFlagProperty<PopupMenuCanceled?>.has('onCanceled', onCanceled));
..add(ObjectFlagProperty<PopupMenuCanceled?>.has('onCanceled', onCanceled))
..add(EnumProperty<ZdsPopupMenuPosition>('menuPosition', menuPosition))
..add(DoubleProperty('verticalOffset', verticalOffset))
..add(DoubleProperty('horizontalOffset', horizontalOffset));
}
}

Expand All @@ -89,9 +120,26 @@ class ZdsPopupMenuState<T> extends State<ZdsPopupMenu<T>> {
}
final RenderBox button = _key.currentContext!.findRenderObject()! as RenderBox;
final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;

final double verticalPosition = switch (widget.menuPosition) {
ZdsPopupMenuPosition.topLeft || ZdsPopupMenuPosition.topRight => 0,
ZdsPopupMenuPosition.bottomLeft || ZdsPopupMenuPosition.bottomRight => button.size.height,
};

final double horizontalPosition = switch (widget.menuPosition) {
ZdsPopupMenuPosition.topLeft || ZdsPopupMenuPosition.bottomLeft => 0,
ZdsPopupMenuPosition.topRight || ZdsPopupMenuPosition.bottomRight => button.size.width,
};

final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(Offset(0, button.size.height), ancestor: overlay),
button.localToGlobal(
Offset(
horizontalPosition + widget.horizontalOffset,
verticalPosition + widget.verticalOffset,
),
ancestor: overlay,
),
button.localToGlobal(
button.size.bottomRight(Offset.zero) + Offset(0, button.size.height),
ancestor: overlay,
Expand Down

0 comments on commit f153842

Please sign in to comment.