From 4c75ec863debcf8400b02449ff874ae179f699fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Poizat?= Date: Mon, 12 Jan 2026 08:15:29 +0100 Subject: [PATCH] Extract AI Scribe menu item component Scribe is always composed of menu (aka category) and submenu (aka action) : - submenu item UI was already in an UI only component. I moved it to items folder. - menu item UI was mixed in a component with desktop related UX. So I extract the UI to create an UI only component. I moved it also to items folder. --- scribe/lib/scribe.dart | 3 +- .../ai_scribe_context_menu_item.dart | 77 ++++--------------- .../widgets/items/ai_scribe_menu_item.dart | 77 +++++++++++++++++++ .../ai_scribe_submenu_item.dart | 0 4 files changed, 93 insertions(+), 64 deletions(-) create mode 100644 scribe/lib/scribe/ai/presentation/widgets/items/ai_scribe_menu_item.dart rename scribe/lib/scribe/ai/presentation/widgets/{context_menu => items}/ai_scribe_submenu_item.dart (100%) diff --git a/scribe/lib/scribe.dart b/scribe/lib/scribe.dart index 06ba7d826..f032511d8 100644 --- a/scribe/lib/scribe.dart +++ b/scribe/lib/scribe.dart @@ -32,7 +32,8 @@ export 'scribe/ai/presentation/widgets/button/inline_ai_assist_button.dart'; export 'scribe/ai/presentation/widgets/context_menu/ai_scribe_context_menu.dart'; export 'scribe/ai/presentation/widgets/context_menu/ai_scribe_context_menu_item.dart'; export 'scribe/ai/presentation/widgets/context_menu/ai_scribe_submenu.dart'; -export 'scribe/ai/presentation/widgets/context_menu/ai_scribe_submenu_item.dart'; +export 'scribe/ai/presentation/widgets/items/ai_scribe_menu_item.dart'; +export 'scribe/ai/presentation/widgets/items/ai_scribe_submenu_item.dart'; export 'scribe/ai/presentation/widgets/modal/ai_scribe_modal_widget.dart'; export 'scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart'; export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_error.dart'; diff --git a/scribe/lib/scribe/ai/presentation/widgets/context_menu/ai_scribe_context_menu_item.dart b/scribe/lib/scribe/ai/presentation/widgets/context_menu/ai_scribe_context_menu_item.dart index 7dd1a9a4f..db7d62331 100644 --- a/scribe/lib/scribe/ai/presentation/widgets/context_menu/ai_scribe_context_menu_item.dart +++ b/scribe/lib/scribe/ai/presentation/widgets/context_menu/ai_scribe_context_menu_item.dart @@ -1,7 +1,5 @@ -import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:scribe/scribe.dart'; class AiScribeContextMenuItem extends StatefulWidget { @@ -40,48 +38,6 @@ class _AiScribeContextMenuItemState extends State { @override Widget build(BuildContext context) { - final childWidget = Container( - key: _itemKey, - height: AIScribeSizes.menuItemHeight, - padding: AIScribeSizes.menuCategoryItemPadding, - alignment: AlignmentDirectional.centerStart, - child: Row( - children: [ - if (widget.menuAction.actionIcon != null) - Padding( - padding: const EdgeInsetsDirectional.only(end: 12), - child: SvgPicture.asset( - widget.menuAction.actionIcon!, - width: 20, - height: 20, - fit: BoxFit.fill, - colorFilter: - AppColor.gray424244.withValues(alpha: 0.72).asFilter(), - ), - ), - Flexible( - child: Text( - widget.menuAction.actionName, - style: AIScribeTextStyles.menuItem, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - if (widget.menuAction.hasSubmenu) - Padding( - padding: const EdgeInsetsDirectional.only(start: 3), - child: SvgPicture.asset( - widget.imagePaths.icArrowRight, - width: 16, - height: 16, - fit: BoxFit.fill, - colorFilter: AppColor.gray777778.asFilter(), - ), - ), - ], - ), - ); - if (widget.menuAction.hasSubmenu) { return MouseRegion( onEnter: (_) { @@ -96,28 +52,23 @@ class _AiScribeContextMenuItemState extends State { onExit: (_) { _hoverController?.exit(); }, - child: Material( - type: MaterialType.transparency, - child: InkWell( - onTap: () => widget.onSelectAction(widget.menuAction), - hoverColor: AppColor.grayBackgroundColor, - child: childWidget, - ), - ), + child: AiScribeMenuItem( + itemKey: _itemKey, + menuAction: widget.menuAction, + onSelectAction: widget.onSelectAction, + imagePaths: widget.imagePaths, + ) ); } - return Material( - type: MaterialType.transparency, - child: InkWell( - onTap: () => widget.onSelectAction(widget.menuAction), - hoverColor: AppColor.grayBackgroundColor, - onHover: (_) { - _hoverController?.exit(); - widget.onHoverOtherItem?.call(); - }, - child: childWidget, - ), + return AiScribeMenuItem( + itemKey: _itemKey, + menuAction: widget.menuAction, + onSelectAction: widget.onSelectAction, + imagePaths: widget.imagePaths, + onHover: (_) { + widget.onHoverOtherItem?.call(); + } ); } diff --git a/scribe/lib/scribe/ai/presentation/widgets/items/ai_scribe_menu_item.dart b/scribe/lib/scribe/ai/presentation/widgets/items/ai_scribe_menu_item.dart new file mode 100644 index 000000000..86e601c79 --- /dev/null +++ b/scribe/lib/scribe/ai/presentation/widgets/items/ai_scribe_menu_item.dart @@ -0,0 +1,77 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:scribe/scribe.dart'; + +typedef OnHoverAction = void Function(bool); + +class AiScribeMenuItem extends StatelessWidget { + final GlobalKey? itemKey; + final AiScribeContextMenuAction menuAction; + final ValueChanged onSelectAction; + final ImagePaths imagePaths; + final OnHoverAction? onHover; + + const AiScribeMenuItem({ + super.key, + this.itemKey, + required this.menuAction, + required this.onSelectAction, + required this.imagePaths, + this.onHover, + }); + + @override + Widget build(BuildContext context) { + return Material( + type: MaterialType.transparency, + child: InkWell( + onTap: () => onSelectAction(menuAction), + hoverColor: AppColor.grayBackgroundColor, + onHover: onHover, + child: Container( + key: itemKey, + height: AIScribeSizes.menuItemHeight, + padding: AIScribeSizes.menuCategoryItemPadding, + alignment: AlignmentDirectional.centerStart, + child: Row( + children: [ + if (menuAction.actionIcon != null) + Padding( + padding: const EdgeInsetsDirectional.only(end: 12), + child: SvgPicture.asset( + menuAction.actionIcon!, + width: 20, + height: 20, + fit: BoxFit.fill, + colorFilter: + AppColor.gray424244.withValues(alpha: 0.72).asFilter(), + ), + ), + Flexible( + child: Text( + menuAction.actionName, + style: AIScribeTextStyles.menuItem, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (menuAction.hasSubmenu) + Padding( + padding: const EdgeInsetsDirectional.only(start: 3), + child: SvgPicture.asset( + imagePaths.icArrowRight, + width: 16, + height: 16, + fit: BoxFit.fill, + colorFilter: AppColor.gray777778.asFilter(), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/scribe/lib/scribe/ai/presentation/widgets/context_menu/ai_scribe_submenu_item.dart b/scribe/lib/scribe/ai/presentation/widgets/items/ai_scribe_submenu_item.dart similarity index 100% rename from scribe/lib/scribe/ai/presentation/widgets/context_menu/ai_scribe_submenu_item.dart rename to scribe/lib/scribe/ai/presentation/widgets/items/ai_scribe_submenu_item.dart