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.
This commit is contained in:
Théo Poizat
2026-01-12 08:15:29 +01:00
committed by Dat H. Pham
parent 08899b1598
commit 4c75ec863d
4 changed files with 93 additions and 64 deletions
+2 -1
View File
@@ -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';
@@ -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<AiScribeContextMenuItem> {
@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<AiScribeContextMenuItem> {
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();
}
);
}
@@ -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<AiScribeContextMenuAction> 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(),
),
),
],
),
),
),
);
}
}