From f66adb53c9b4e469292f183efdff69efd993ed3b Mon Sep 17 00:00:00 2001
From: appflowy <annie@appflowy.io>
Date: Thu, 21 Apr 2022 15:59:56 +0800
Subject: [PATCH] chore: config hover

---
 .../app_flowy/assets/translations/en.json     |   3 +-
 .../app_flowy/lib/startup/deps_resolver.dart  |   3 +-
 .../grid/cell/checkbox_cell_bloc.dart         |  10 +-
 .../application/grid/cell/date_cell_bloc.dart |   8 +-
 .../application/grid/cell/text_cell_bloc.dart |  56 ++++++--
 .../application/grid/row/row_detail_bloc.dart |  19 +--
 .../home/menu/app/header/add_button.dart      |  18 ++-
 .../presentation/home/menu/app/menu_app.dart  |   8 +-
 .../plugins/grid/src/grid_page.dart           |   4 +-
 .../grid/src/widgets/cell/cell_builder.dart   | 121 ++++++++++++++++-
 .../grid/src/widgets/cell/cell_container.dart |  49 ++-----
 .../grid/src/widgets/cell/checkbox_cell.dart  |   5 +-
 .../grid/src/widgets/cell/date_cell.dart      |  13 +-
 .../grid/src/widgets/cell/number_cell.dart    |  15 ++-
 .../cell/selection_cell/selection_cell.dart   |  14 +-
 .../grid/src/widgets/cell/text_cell.dart      |  56 ++++++--
 .../grid/src/widgets/header/field_cell.dart   |  41 ++++--
 .../grid/src/widgets/header/field_editor.dart |   4 +-
 .../grid/src/widgets/row/row_detail.dart      | 119 +++++++++++++++--
 .../presentation/widgets/pop_up_action.dart   |  42 +++---
 .../lib/style_widget/hover.dart               | 124 ++++++++++++++++--
 21 files changed, 564 insertions(+), 168 deletions(-)

diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json
index 97b0d162ef..a051401446 100644
--- a/frontend/app_flowy/assets/translations/en.json
+++ b/frontend/app_flowy/assets/translations/en.json
@@ -177,7 +177,8 @@
     },
     "row": {
       "duplicate": "Duplicate",
-      "delete": "Delete"
+      "delete": "Delete",
+      "textPlaceholder": "Empty"
     },
     "selectOption": {
       "purpleColor": "Purple",
diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart
index 2fd03d639f..8f239f1ece 100644
--- a/frontend/app_flowy/lib/startup/deps_resolver.dart
+++ b/frontend/app_flowy/lib/startup/deps_resolver.dart
@@ -170,7 +170,6 @@ void _resolveGridDeps(GetIt getIt) {
 
   getIt.registerFactoryParam<TextCellBloc, GridCell, void>(
     (cellData, _) => TextCellBloc(
-      service: CellService(),
       cellData: cellData,
     ),
   );
@@ -189,7 +188,7 @@ void _resolveGridDeps(GetIt getIt) {
 
   getIt.registerFactoryParam<DateCellBloc, GridCell, void>(
     (cellData, _) => DateCellBloc(
-      cellIdentifier: cellData,
+      cellData: cellData,
     ),
   );
 
diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart
index 6d2935458a..880267f6c9 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart
@@ -11,13 +11,13 @@ part 'checkbox_cell_bloc.freezed.dart';
 
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
   final CellService _service;
-  final CellListener _listener;
+  final CellListener _cellListener;
 
   CheckboxCellBloc({
     required CellService service,
     required GridCell cellData,
   })  : _service = service,
-        _listener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
+        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
         super(CheckboxCellState.initial(cellData)) {
     on<CheckboxCellEvent>(
       (event, emit) async {
@@ -38,18 +38,18 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
 
   @override
   Future<void> close() async {
-    await _listener.stop();
+    await _cellListener.stop();
     return super.close();
   }
 
   void _startListening() {
-    _listener.updateCellNotifier?.addPublishListener((result) {
+    _cellListener.updateCellNotifier?.addPublishListener((result) {
       result.fold(
         (notificationData) async => await _loadCellData(),
         (err) => Log.error(err),
       );
     });
-    _listener.start();
+    _cellListener.start();
   }
 
   Future<void> _loadCellData() async {
diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart
index 532b67ae3a..cbabe6c5fe 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart
@@ -15,11 +15,11 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
   final CellListener _cellListener;
   final SingleFieldListener _fieldListener;
 
-  DateCellBloc({required GridCell cellIdentifier})
+  DateCellBloc({required GridCell cellData})
       : _service = CellService(),
-        _cellListener = CellListener(rowId: cellIdentifier.rowId, fieldId: cellIdentifier.field.id),
-        _fieldListener = SingleFieldListener(fieldId: cellIdentifier.field.id),
-        super(DateCellState.initial(cellIdentifier)) {
+        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
+        _fieldListener = SingleFieldListener(fieldId: cellData.field.id),
+        super(DateCellState.initial(cellData)) {
     on<DateCellEvent>(
       (event, emit) async {
         event.map(
diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart
index ec78136dd3..f32c066c5e 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart
@@ -1,22 +1,29 @@
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
+import 'cell_listener.dart';
 import 'cell_service.dart';
 
 part 'text_cell_bloc.freezed.dart';
 
 class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
-  final CellService service;
+  final CellService _service;
+  final CellListener _cellListener;
 
   TextCellBloc({
-    required this.service,
     required GridCell cellData,
-  }) : super(TextCellState.initial(cellData)) {
+  })  : _service = CellService(),
+        _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
+        super(TextCellState.initial(cellData)) {
     on<TextCellEvent>(
       (event, emit) async {
         await event.map(
-          initial: (_InitialCell value) async {},
+          initial: (_InitialCell value) async {
+            _startListening();
+          },
           updateText: (_UpdateText value) {
             updateCellContent(value.text);
             emit(state.copyWith(content: value.text));
@@ -27,16 +34,28 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
               content: value.cellData.cell?.content ?? "",
             ));
           },
+          didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
+            emit(state.copyWith(
+              cellData: state.cellData.copyWith(cell: value.cell),
+              content: value.cell.content,
+            ));
+          },
         );
       },
     );
   }
 
+  @override
+  Future<void> close() async {
+    await _cellListener.stop();
+    return super.close();
+  }
+
   void updateCellContent(String content) {
     final fieldId = state.cellData.field.id;
     final gridId = state.cellData.gridId;
     final rowId = state.cellData.rowId;
-    service.updateCell(
+    _service.updateCell(
       data: content,
       fieldId: fieldId,
       gridId: gridId,
@@ -44,9 +63,29 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
     );
   }
 
-  @override
-  Future<void> close() async {
-    return super.close();
+  void _startListening() {
+    _cellListener.updateCellNotifier?.addPublishListener((result) {
+      result.fold(
+        (notificationData) async => await _loadCellData(),
+        (err) => Log.error(err),
+      );
+    });
+    _cellListener.start();
+  }
+
+  Future<void> _loadCellData() async {
+    final result = await _service.getCell(
+      gridId: state.cellData.gridId,
+      fieldId: state.cellData.field.id,
+      rowId: state.cellData.rowId,
+    );
+    if (isClosed) {
+      return;
+    }
+    result.fold(
+      (cell) => add(TextCellEvent.didReceiveCellUpdate(cell)),
+      (err) => Log.error(err),
+    );
   }
 }
 
@@ -54,6 +93,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
 class TextCellEvent with _$TextCellEvent {
   const factory TextCellEvent.initial() = _InitialCell;
   const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData;
+  const factory TextCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
   const factory TextCellEvent.updateText(String text) = _UpdateText;
 }
 
diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart
index e93f0be89b..1ba10f7614 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart
@@ -22,8 +22,11 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
         await event.map(
           initial: (_Initial value) async {
             await _startListening();
+            _loadCellData();
+          },
+          didReceiveCellDatas: (_DidReceiveCellDatas value) {
+            emit(state.copyWith(cellDatas: value.cellDatas));
           },
-          didReceiveCellDatas: (_DidReceiveCellDatas value) {},
         );
       },
     );
@@ -40,16 +43,16 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
   Future<void> _startListening() async {
     _rowListenFn = _rowCache.addRowListener(
       rowId: rowData.rowId,
-      onUpdated: (cellDatas) => add(RowDetailEvent.didReceiveCellDatas(cellDatas)),
+      onUpdated: (cellDatas) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
       listenWhen: () => !isClosed,
     );
   }
 
-  Future<void> _loadRow(Emitter<RowDetailState> emit) async {
+  Future<void> _loadCellData() async {
     final data = _rowCache.loadCellData(rowData.rowId);
-    data.foldRight(null, (cellDatas, _) {
+    data.foldRight(null, (cellDataMap, _) {
       if (!isClosed) {
-        add(RowDetailEvent.didReceiveCellDatas(cellDatas));
+        add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList()));
       }
     });
   }
@@ -58,16 +61,16 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
 @freezed
 class RowDetailEvent with _$RowDetailEvent {
   const factory RowDetailEvent.initial() = _Initial;
-  const factory RowDetailEvent.didReceiveCellDatas(CellDataMap cellData) = _DidReceiveCellDatas;
+  const factory RowDetailEvent.didReceiveCellDatas(List<GridCell> cellDatas) = _DidReceiveCellDatas;
 }
 
 @freezed
 class RowDetailState with _$RowDetailState {
   const factory RowDetailState({
-    required Option<CellDataMap> cellDataMap,
+    required List<GridCell> cellDatas,
   }) = _RowDetailState;
 
   factory RowDetailState.initial() => RowDetailState(
-        cellDataMap: none(),
+        cellDatas: List.empty(),
       );
 }
diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart
index 214dc7a198..a7de233006 100644
--- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart
@@ -82,16 +82,14 @@ class CreateItem extends StatelessWidget {
 
     return FlowyHover(
       style: config,
-      builder: (context, onHover) {
-        return GestureDetector(
-          onTap: () => onSelected(pluginBuilder),
-          child: FlowyText.medium(
-            pluginBuilder.menuName,
-            color: theme.textColor,
-            fontSize: 12,
-          ).padding(horizontal: 10, vertical: 6),
-        );
-      },
+      child: GestureDetector(
+        onTap: () => onSelected(pluginBuilder),
+        child: FlowyText.medium(
+          pluginBuilder.menuName,
+          color: theme.textColor,
+          fontSize: 12,
+        ).padding(horizontal: 10, vertical: 6),
+      ),
     );
   }
 }
diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart
index 41ba6ab4d1..d028355ed1 100644
--- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart
@@ -95,9 +95,11 @@ class _MenuAppState extends State<MenuApp> {
   Widget _renderViewSection(AppDataNotifier notifier) {
     return MultiProvider(
       providers: [ChangeNotifierProvider.value(value: notifier)],
-      child: Consumer(builder: (context, AppDataNotifier notifier, child) {
-        return ViewSection(appData: notifier);
-      }),
+      child: Consumer(
+        builder: (context, AppDataNotifier notifier, child) {
+          return ViewSection(appData: notifier);
+        },
+      ),
     );
   }
 
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
index b3d35977b8..0fc69efc42 100755
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
@@ -1,6 +1,5 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
-import 'package:app_flowy/workspace/application/grid/row/row_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
@@ -214,8 +213,7 @@ class _GridRowsState extends State<_GridRows> {
           key: _key,
           initialItemCount: context.read<GridBloc>().state.rows.length,
           itemBuilder: (BuildContext context, int index, Animation<double> animation) {
-            final rowData = context.read<GridBloc>().state.rows[index];
-            return _renderRow(context, rowData, animation);
+            return _renderRow(context, state.rows[index], animation);
           },
         );
       },
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
index 94f2e0da70..3304de6e51 100755
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
@@ -1,13 +1,17 @@
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
+import 'package:flowy_infra/size.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
 import 'package:flutter/widgets.dart';
+import 'package:provider/provider.dart';
 import 'checkbox_cell.dart';
 import 'date_cell.dart';
 import 'number_cell.dart';
 import 'selection_cell/selection_cell.dart';
 import 'text_cell.dart';
 
-Widget buildGridCell(GridCell cellData) {
+GridCellWidget buildGridCell(GridCell cellData, {GridCellStyle? style}) {
   final key = ValueKey(cellData.field.id + cellData.rowId);
   switch (cellData.field.fieldType) {
     case FieldType.Checkbox:
@@ -19,7 +23,7 @@ Widget buildGridCell(GridCell cellData) {
     case FieldType.Number:
       return NumberCell(cellData: cellData, key: key);
     case FieldType.RichText:
-      return GridTextCell(cellData: cellData, key: key);
+      return GridTextCell(cellData: cellData, key: key, style: style);
     case FieldType.SingleSelect:
       return SingleSelectCell(cellData: cellData, key: key);
     default:
@@ -35,3 +39,116 @@ class BlankCell extends StatelessWidget {
     return Container();
   }
 }
+
+abstract class GridCellWidget extends HoverWidget {
+  @override
+  final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
+  GridCellWidget({Key? key}) : super(key: key);
+}
+
+abstract class B {
+  ValueNotifier<bool> get onFocus;
+}
+
+abstract class GridCellStyle {}
+
+//
+abstract class HoverWidget extends StatefulWidget {
+  const HoverWidget({Key? key}) : super(key: key);
+
+  ValueNotifier<bool> get onFocus;
+}
+
+class FlowyHover2 extends StatefulWidget {
+  final HoverWidget child;
+  const FlowyHover2({required this.child, Key? key}) : super(key: key);
+
+  @override
+  State<FlowyHover2> createState() => _FlowyHover2State();
+}
+
+class _FlowyHover2State extends State<FlowyHover2> {
+  late FlowyHoverState _hoverState;
+
+  @override
+  void initState() {
+    _hoverState = FlowyHoverState();
+    widget.child.onFocus.addListener(() {
+      _hoverState.onFocus = widget.child.onFocus.value;
+    });
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    _hoverState.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return ChangeNotifierProvider.value(
+      value: _hoverState,
+      child: MouseRegion(
+        cursor: SystemMouseCursors.click,
+        opaque: false,
+        onEnter: (p) => setState(() => _hoverState.onHover = true),
+        onExit: (p) => setState(() => _hoverState.onHover = false),
+        child: Stack(
+          fit: StackFit.expand,
+          alignment: AlignmentDirectional.center,
+          children: [
+            const _HoverBackground(),
+            widget.child,
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+class _HoverBackground extends StatelessWidget {
+  const _HoverBackground({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return Consumer<FlowyHoverState>(
+      builder: (context, state, child) {
+        if (state.onHover || state.onFocus) {
+          return FlowyHoverContainer(
+            style: HoverStyle(
+              borderRadius: Corners.s6Border,
+              hoverColor: theme.shader6,
+            ),
+          );
+        } else {
+          return const SizedBox();
+        }
+      },
+    );
+  }
+}
+
+class FlowyHoverState extends ChangeNotifier {
+  bool _onHover = false;
+  bool _onFocus = false;
+
+  set onHover(bool value) {
+    if (_onHover != value) {
+      _onHover = value;
+      notifyListeners();
+    }
+  }
+
+  bool get onHover => _onHover;
+
+  set onFocus(bool value) {
+    if (_onFocus != value) {
+      _onFocus = value;
+      notifyListeners();
+    }
+  }
+
+  bool get onFocus => _onFocus;
+}
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart
index 243edae3a9..17c1e87710 100755
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart
@@ -1,8 +1,8 @@
 import 'package:flowy_infra/theme.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
-
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'cell_builder.dart';
 
 class CellStateNotifier extends ChangeNotifier {
   bool _isFocus = false;
@@ -28,7 +28,7 @@ class CellStateNotifier extends ChangeNotifier {
 }
 
 class CellContainer extends StatelessWidget {
-  final Widget child;
+  final GridCellWidget child;
   final Widget? expander;
   final double width;
   const CellContainer({
@@ -46,6 +46,9 @@ class CellContainer extends StatelessWidget {
         selector: (context, notifier) => notifier.isFocus,
         builder: (context, isFocus, _) {
           Widget container = Center(child: child);
+          child.onFocus.addListener(() {
+            Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.onFocus.value;
+          });
 
           if (expander != null) {
             container = _CellEnterRegion(child: container, expander: expander!);
@@ -75,16 +78,16 @@ class CellContainer extends StatelessWidget {
 }
 
 class _CellEnterRegion extends StatelessWidget {
-  final Widget expander;
   final Widget child;
-  const _CellEnterRegion({required this.expander, required this.child, Key? key}) : super(key: key);
+  final Widget expander;
+  const _CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     return Selector<CellStateNotifier, bool>(
       selector: (context, notifier) => notifier.onEnter,
       builder: (context, onEnter, _) {
-        List<Widget> children = [child];
+        List<Widget> children = [Expanded(child: child)];
         if (onEnter) {
           children.add(expander);
         }
@@ -93,8 +96,8 @@ class _CellEnterRegion extends StatelessWidget {
           cursor: SystemMouseCursors.click,
           onEnter: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = true,
           onExit: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = false,
-          child: Stack(
-            alignment: AlignmentDirectional.centerEnd,
+          child: Row(
+            // alignment: AlignmentDirectional.centerEnd,
             children: children,
           ),
         );
@@ -102,35 +105,3 @@ class _CellEnterRegion extends StatelessWidget {
     );
   }
 }
-
-abstract class GridCellWidget extends StatefulWidget {
-  const GridCellWidget({Key? key}) : super(key: key);
-
-  void setFocus(BuildContext context, bool value) {
-    Provider.of<CellStateNotifier>(context, listen: false).isFocus = value;
-  }
-}
-
-class CellFocusNode extends FocusNode {
-  VoidCallback? focusCallback;
-
-  void addCallback(BuildContext context, VoidCallback callback) {
-    if (focusCallback != null) {
-      removeListener(focusCallback!);
-    }
-    focusCallback = () {
-      Provider.of<CellStateNotifier>(context, listen: false).isFocus = hasFocus;
-      callback();
-    };
-
-    addListener(focusCallback!);
-  }
-
-  @override
-  void dispose() {
-    if (focusCallback != null) {
-      removeListener(focusCallback!);
-    }
-    super.dispose();
-  }
-}
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart
index cbf8d0fadb..608913d41d 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart
@@ -4,11 +4,12 @@ import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'cell_builder.dart';
 
-class CheckboxCell extends StatefulWidget {
+class CheckboxCell extends GridCellWidget {
   final GridCell cellData;
 
-  const CheckboxCell({
+  CheckboxCell({
     required this.cellData,
     Key? key,
   }) : super(key: key);
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart
index dcd2b2f5c2..84d60c797a 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart
@@ -1,17 +1,22 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:table_calendar/table_calendar.dart';
+import 'cell_builder.dart';
+
+abstract class GridCellDelegate {
+  void onFocus(bool isFocus);
+  GridCellDelegate get delegate;
+}
 
 class DateCell extends GridCellWidget {
   final GridCell cellData;
 
-  const DateCell({
+  DateCell({
     required this.cellData,
     Key? key,
   }) : super(key: key);
@@ -39,13 +44,13 @@ class _DateCellState extends State<DateCell> {
             child: GestureDetector(
               behavior: HitTestBehavior.opaque,
               onTap: () {
-                widget.setFocus(context, true);
+                widget.onFocus.value = true;
                 _CellCalendar.show(
                   context,
                   onSelected: (day) {
                     context.read<DateCellBloc>().add(DateCellEvent.selectDay(day));
                   },
-                  onDismissed: () => widget.setFocus(context, false),
+                  onDismissed: () => widget.onFocus.value = false,
                 );
               },
               child: MouseRegion(
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart
index 09331e49b5..2d98aa0bd6 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart
@@ -2,14 +2,15 @@ import 'dart:async';
 
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
+import 'cell_builder.dart';
+
 class NumberCell extends GridCellWidget {
   final GridCell cellData;
 
-  const NumberCell({
+  NumberCell({
     required this.cellData,
     Key? key,
   }) : super(key: key);
@@ -21,21 +22,23 @@ class NumberCell extends GridCellWidget {
 class _NumberCellState extends State<NumberCell> {
   late NumberCellBloc _cellBloc;
   late TextEditingController _controller;
-  late CellFocusNode _focusNode;
+  late FocusNode _focusNode;
   Timer? _delayOperation;
 
   @override
   void initState() {
     _cellBloc = getIt<NumberCellBloc>(param1: widget.cellData)..add(const NumberCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);
-    _focusNode = CellFocusNode();
+    _focusNode = FocusNode();
+    _focusNode.addListener(() {
+      widget.onFocus.value = _focusNode.hasFocus;
+      focusChanged();
+    });
     super.initState();
   }
 
   @override
   Widget build(BuildContext context) {
-    _focusNode.addCallback(context, focusChanged);
-
     return BlocProvider.value(
       value: _cellBloc,
       child: BlocConsumer<NumberCellBloc, NumberCellState>(
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart
index 1261a4e5f4..05dd66e224 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart
@@ -1,6 +1,6 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
@@ -10,7 +10,7 @@ import 'selection_editor.dart';
 class SingleSelectCell extends GridCellWidget {
   final GridCell cellData;
 
-  const SingleSelectCell({
+  SingleSelectCell({
     required this.cellData,
     Key? key,
   }) : super(key: key);
@@ -38,13 +38,13 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
           return SizedBox.expand(
             child: InkWell(
               onTap: () {
-                widget.setFocus(context, true);
+                widget.onFocus.value = true;
                 SelectOptionCellEditor.show(
                   context,
                   state.cellData,
                   state.options,
                   state.selectedOptions,
-                  () => widget.setFocus(context, false),
+                  () => widget.onFocus.value = false,
                 );
               },
               child: ClipRRect(child: Row(children: children)),
@@ -66,7 +66,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
 class MultiSelectCell extends GridCellWidget {
   final GridCell cellData;
 
-  const MultiSelectCell({
+  MultiSelectCell({
     required this.cellData,
     Key? key,
   }) : super(key: key);
@@ -94,13 +94,13 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
           return SizedBox.expand(
             child: InkWell(
               onTap: () {
-                widget.setFocus(context, true);
+                widget.onFocus.value = true;
                 SelectOptionCellEditor.show(
                   context,
                   state.cellData,
                   state.options,
                   state.selectedOptions,
-                  () => widget.setFocus(context, false),
+                  () => widget.onFocus.value = false,
                 );
               },
               child: ClipRRect(child: Row(children: children)),
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart
index 93fb4b15e7..688f8344f7 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart
@@ -1,16 +1,39 @@
 import 'dart:async';
-import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'cell_container.dart';
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
+import 'cell_builder.dart';
+
+class GridTextCellStyle extends GridCellStyle {
+  String? placeholder;
+  Color? hoverColor;
+  bool filled;
+  InputBorder? inputBorder;
+  EdgeInsets? contentPadding;
+  GridTextCellStyle({
+    this.placeholder,
+    this.hoverColor,
+    this.filled = false,
+    this.inputBorder,
+    this.contentPadding,
+  });
+}
 
 class GridTextCell extends GridCellWidget {
   final GridCell cellData;
-  const GridTextCell({
+  late final GridTextCellStyle? cellStyle;
+  GridTextCell({
     required this.cellData,
+    GridCellStyle? style,
     Key? key,
-  }) : super(key: key);
+  }) : super(key: key) {
+    if (style != null) {
+      cellStyle = (style as GridTextCellStyle);
+    } else {
+      cellStyle = null;
+    }
+  }
 
   @override
   State<GridTextCell> createState() => _GridTextCellState();
@@ -19,21 +42,25 @@ class GridTextCell extends GridCellWidget {
 class _GridTextCellState extends State<GridTextCell> {
   late TextCellBloc _cellBloc;
   late TextEditingController _controller;
-  late CellFocusNode _focusNode;
+  late FocusNode _focusNode;
+
   Timer? _delayOperation;
 
   @override
   void initState() {
     _cellBloc = getIt<TextCellBloc>(param1: widget.cellData);
+    _cellBloc.add(const TextCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);
-    _focusNode = CellFocusNode();
+    _focusNode = FocusNode();
+    _focusNode.addListener(() {
+      widget.onFocus.value = _focusNode.hasFocus;
+      focusChanged();
+    });
     super.initState();
   }
 
   @override
   Widget build(BuildContext context) {
-    _focusNode.addCallback(context, focusChanged);
-
     return BlocProvider.value(
       value: _cellBloc,
       child: BlocConsumer<TextCellBloc, TextCellState>(
@@ -42,6 +69,7 @@ class _GridTextCellState extends State<GridTextCell> {
             _controller.text = state.content;
           }
         },
+        buildWhen: (previous, current) => previous.content != current.content,
         builder: (context, state) {
           return TextField(
             controller: _controller,
@@ -50,9 +78,13 @@ class _GridTextCellState extends State<GridTextCell> {
             onEditingComplete: () => _focusNode.unfocus(),
             maxLines: 1,
             style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
-            decoration: const InputDecoration(
-              contentPadding: EdgeInsets.zero,
-              border: InputBorder.none,
+            decoration: InputDecoration(
+              contentPadding: widget.cellStyle?.contentPadding ?? EdgeInsets.zero,
+              border: widget.cellStyle?.inputBorder ?? InputBorder.none,
+              hintText: widget.cellStyle?.placeholder,
+              hoverColor: widget.cellStyle?.hoverColor ?? Colors.transparent,
+              filled: widget.cellStyle?.filled ?? false,
+              fillColor: Colors.transparent,
               isDense: true,
             ),
           );
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart
index c6009b64af..aa45487fe2 100755
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart
@@ -7,6 +7,7 @@ import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'field_type_extension.dart';
@@ -20,21 +21,21 @@ class GridFieldCell extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-
     return BlocProvider(
       create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
       child: BlocBuilder<FieldCellBloc, FieldCellState>(
         builder: (context, state) {
-          final button = FlowyButton(
-            hoverColor: theme.shader6,
+          final button = FieldCellButton(
+            field: state.field,
             onTap: () => _showActionSheet(context),
-            leftIcon: svgWidget(state.field.fieldType.iconName(), color: theme.iconColor),
-            text: FlowyText.medium(state.field.name, fontSize: 12),
-            padding: GridSize.cellContentInsets,
           );
 
-          const line = Positioned(top: 0, bottom: 0, right: 0, child: _DragToExpandLine());
+          const line = Positioned(
+            top: 0,
+            bottom: 0,
+            right: 0,
+            child: _DragToExpandLine(),
+          );
 
           return _CellContainer(
             width: state.field.width.toDouble(),
@@ -125,9 +126,31 @@ class _DragToExpandLine extends StatelessWidget {
             borderRadius: BorderRadius.zero,
             contentMargin: const EdgeInsets.only(left: 5),
           ),
-          builder: (_, onHover) => const SizedBox(width: 2),
+          child: const SizedBox(width: 2),
         ),
       ),
     );
   }
 }
+
+class FieldCellButton extends StatelessWidget {
+  final VoidCallback onTap;
+  final Field field;
+  const FieldCellButton({
+    required this.field,
+    required this.onTap,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return FlowyButton(
+      hoverColor: theme.shader6,
+      onTap: onTap,
+      leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
+      text: FlowyText.medium(field.name, fontSize: 12),
+      padding: GridSize.cellContentInsets,
+    );
+  }
+}
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
index f503877a4a..6efe439399 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
@@ -2,12 +2,14 @@ import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_switch_bloc.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'field_name_input.dart';
 import 'field_switcher.dart';
 
@@ -70,7 +72,7 @@ class _FieldEditorWidget extends StatelessWidget {
             (field) => ListView(
               shrinkWrap: true,
               children: [
-                const FlowyText.medium("Edit property", fontSize: 12),
+                FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
                 const VSpace(10),
                 const _FieldNameTextField(),
                 const VSpace(10),
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
index e41a4b484b..99e9f108cd 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart
@@ -1,11 +1,19 @@
 import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
+import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
+import 'package:easy_localization/easy_localization.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:window_size/window_size.dart';
 
-class RowDetailPage extends StatelessWidget with FlowyOverlayDelegate {
+class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
   final GridRow rowData;
   final GridRowCache rowCache;
 
@@ -16,24 +24,17 @@ class RowDetailPage extends StatelessWidget with FlowyOverlayDelegate {
   }) : super(key: key);
 
   @override
-  Widget build(BuildContext context) {
-    return BlocProvider(
-      create: (context) => RowDetailBloc(rowData: rowData, rowCache: rowCache),
-      child: Container(),
-    );
-  }
+  State<RowDetailPage> createState() => _RowDetailPageState();
 
   void show(BuildContext context) async {
-    FlowyOverlay.of(context).remove(identifier());
-
-    const size = Size(460, 400);
     final window = await getWindowInfo();
+    final size = Size(window.frame.size.width * 0.7, window.frame.size.height * 0.7);
     FlowyOverlay.of(context).insertWithRect(
       widget: OverlayContainer(
         child: this,
-        constraints: BoxConstraints.tight(const Size(460, 400)),
+        constraints: BoxConstraints.tight(size),
       ),
-      identifier: identifier(),
+      identifier: RowDetailPage.identifier(),
       anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
       anchorSize: window.frame.size,
       anchorDirection: AnchorDirection.center,
@@ -46,3 +47,97 @@ class RowDetailPage extends StatelessWidget with FlowyOverlayDelegate {
     return (RowDetailPage).toString();
   }
 }
+
+class _RowDetailPageState extends State<RowDetailPage> {
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider(
+      create: (context) {
+        final bloc = RowDetailBloc(rowData: widget.rowData, rowCache: widget.rowCache);
+        bloc.add(const RowDetailEvent.initial());
+        return bloc;
+      },
+      child: const Padding(
+        padding: EdgeInsets.symmetric(horizontal: 80, vertical: 40),
+        child: _PropertyList(),
+      ),
+    );
+  }
+}
+
+class _PropertyList extends StatelessWidget {
+  const _PropertyList({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<RowDetailBloc, RowDetailState>(
+      buildWhen: (previous, current) => previous.cellDatas != current.cellDatas,
+      builder: (context, state) {
+        return ListView.separated(
+          itemCount: state.cellDatas.length,
+          itemBuilder: (BuildContext context, int index) {
+            return _RowDetailCell(cellData: state.cellDatas[index]);
+          },
+          separatorBuilder: (BuildContext context, int index) {
+            return const VSpace(2);
+          },
+        );
+      },
+    );
+  }
+}
+
+class _RowDetailCell extends StatelessWidget {
+  final GridCell cellData;
+  const _RowDetailCell({required this.cellData, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    final cell = buildGridCell(
+      cellData,
+      style: _buildCellStyle(theme, cellData.field.fieldType),
+    );
+    return SizedBox(
+      height: 36,
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: [
+          SizedBox(
+            width: 150,
+            child: FieldCellButton(field: cellData.field, onTap: () {}),
+          ),
+          const HSpace(10),
+          Expanded(child: FlowyHover2(child: cell)),
+        ],
+      ),
+    );
+  }
+}
+
+GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
+  switch (fieldType) {
+    case FieldType.Checkbox:
+      return null;
+    case FieldType.DateTime:
+      return null;
+    case FieldType.MultiSelect:
+      return null;
+    case FieldType.Number:
+      return null;
+    case FieldType.RichText:
+      return GridTextCellStyle(
+        hoverColor: theme.shader6,
+        filled: true,
+        placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+        contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
+      );
+    case FieldType.SingleSelect:
+      return null;
+    default:
+      return null;
+  }
+}
diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart
index cc822a6ef9..cbcec14fee 100644
--- a/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart
@@ -86,29 +86,27 @@ class ActionCell<T extends ActionItem> extends StatelessWidget {
 
     return FlowyHover(
       style: HoverStyle(hoverColor: theme.hover),
-      builder: (context, onHover) {
-        return GestureDetector(
-          behavior: HitTestBehavior.opaque,
-          onTap: () => onSelected(action),
-          child: SizedBox(
-            height: itemHeight,
-            child: Row(
-              crossAxisAlignment: CrossAxisAlignment.center,
-              children: [
-                if (action.icon != null) action.icon!,
-                HSpace(ActionListSizes.itemHPadding),
-                FlowyText.medium(
-                  action.name,
-                  fontSize: 12,
-                ),
-              ],
-            ),
-          ).padding(
-            horizontal: ActionListSizes.padding,
-            vertical: ActionListSizes.padding,
+      child: GestureDetector(
+        behavior: HitTestBehavior.opaque,
+        onTap: () => onSelected(action),
+        child: SizedBox(
+          height: itemHeight,
+          child: Row(
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              if (action.icon != null) action.icon!,
+              HSpace(ActionListSizes.itemHPadding),
+              FlowyText.medium(
+                action.name,
+                fontSize: 12,
+              ),
+            ],
           ),
-        );
-      },
+        ).padding(
+          horizontal: ActionListSizes.padding,
+          vertical: ActionListSizes.padding,
+        ),
+      ),
     );
   }
 }
diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart
index f8caf35b84..e7c7b183cf 100644
--- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart
+++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart
@@ -1,17 +1,22 @@
 import 'package:flutter/material.dart';
 // ignore: unused_import
 import 'package:flowy_infra/time/duration.dart';
+import 'package:flowy_infra/size.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:provider/provider.dart';
 
 typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
 
 class FlowyHover extends StatefulWidget {
   final HoverStyle style;
-  final HoverBuilder builder;
+  final HoverBuilder? builder;
+  final Widget? child;
   final bool Function()? setSelected;
 
   const FlowyHover({
     Key? key,
-    required this.builder,
+    this.builder,
+    this.child,
     required this.style,
     this.setSelected,
   }) : super(key: key);
@@ -27,25 +32,27 @@ class _FlowyHoverState extends State<FlowyHover> {
   Widget build(BuildContext context) {
     return MouseRegion(
       cursor: SystemMouseCursors.click,
+      opaque: false,
       onEnter: (p) => setState(() => _onHover = true),
       onExit: (p) => setState(() => _onHover = false),
-      child: render(),
+      child: renderWidget(),
     );
   }
 
-  Widget render() {
+  Widget renderWidget() {
     var showHover = _onHover;
     if (!showHover && widget.setSelected != null) {
       showHover = widget.setSelected!();
     }
 
+    final child = widget.child ?? widget.builder!(context, _onHover);
     if (showHover) {
       return FlowyHoverContainer(
         style: widget.style,
-        child: widget.builder(context, _onHover),
+        child: child,
       );
     } else {
-      return widget.builder(context, _onHover);
+      return child;
     }
   }
 }
@@ -67,11 +74,11 @@ class HoverStyle {
 
 class FlowyHoverContainer extends StatelessWidget {
   final HoverStyle style;
-  final Widget child;
+  final Widget? child;
 
   const FlowyHoverContainer({
     Key? key,
-    required this.child,
+    this.child,
     required this.style,
   }) : super(key: key);
 
@@ -93,3 +100,104 @@ class FlowyHoverContainer extends StatelessWidget {
     );
   }
 }
+
+//
+abstract class HoverWidget extends StatefulWidget {
+  const HoverWidget({Key? key}) : super(key: key);
+
+  ValueNotifier<bool> get onFocus;
+}
+
+class FlowyHover2 extends StatefulWidget {
+  final HoverWidget child;
+  const FlowyHover2({required this.child, Key? key}) : super(key: key);
+
+  @override
+  State<FlowyHover2> createState() => _FlowyHover2State();
+}
+
+class _FlowyHover2State extends State<FlowyHover2> {
+  late FlowyHoverState _hoverState;
+
+  @override
+  void initState() {
+    _hoverState = FlowyHoverState();
+    widget.child.onFocus.addListener(() {
+      _hoverState.onFocus = widget.child.onFocus.value;
+    });
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    _hoverState.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return ChangeNotifierProvider.value(
+      value: _hoverState,
+      child: MouseRegion(
+        cursor: SystemMouseCursors.click,
+        opaque: false,
+        onEnter: (p) => setState(() => _hoverState.onHover = true),
+        onExit: (p) => setState(() => _hoverState.onHover = false),
+        child: Stack(
+          fit: StackFit.expand,
+          alignment: AlignmentDirectional.center,
+          children: [
+            const _HoverBackground(),
+            widget.child,
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+class _HoverBackground extends StatelessWidget {
+  const _HoverBackground({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return Consumer<FlowyHoverState>(
+      builder: (context, state, child) {
+        if (state.onHover || state.onFocus) {
+          return FlowyHoverContainer(
+            style: HoverStyle(
+              borderRadius: Corners.s6Border,
+              hoverColor: theme.shader6,
+            ),
+          );
+        } else {
+          return const SizedBox();
+        }
+      },
+    );
+  }
+}
+
+class FlowyHoverState extends ChangeNotifier {
+  bool _onHover = false;
+  bool _onFocus = false;
+
+  set onHover(bool value) {
+    if (_onHover != value) {
+      _onHover = value;
+      notifyListeners();
+    }
+  }
+
+  bool get onHover => _onHover;
+
+  set onFocus(bool value) {
+    if (_onFocus != value) {
+      _onFocus = value;
+      notifyListeners();
+    }
+  }
+
+  bool get onFocus => _onFocus;
+}