Browse Source

chore: config option editor ui

appflowy 3 years ago
parent
commit
5358203a46
27 changed files with 401 additions and 155 deletions
  1. 2 1
      frontend/app_flowy/assets/translations/en.json
  2. 6 6
      frontend/app_flowy/lib/startup/deps_resolver.dart
  3. 2 2
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart
  4. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart
  5. 1 1
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart
  6. 54 9
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart
  7. 27 16
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart
  8. 4 4
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart
  9. 4 4
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  10. 4 4
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  11. 2 4
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  12. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  13. 7 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart
  14. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart
  15. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart
  16. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart
  17. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart
  18. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart
  19. 53 35
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart
  20. 12 14
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart
  21. 124 38
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart
  22. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart
  23. 44 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart
  24. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart
  25. 44 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/number_cell.dart
  26. 1 1
      frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart
  27. 1 1
      shared-lib/flowy-grid-data-model/src/entities/meta.rs

+ 2 - 1
frontend/app_flowy/assets/translations/en.json

@@ -186,7 +186,8 @@
       "blueColor": "Blue",
       "deleteTag": "Delete tag",
       "colorPannelTitle": "Colors",
-      "pannelTitle": "Select an option"
+      "pannelTitle": "Select an option or create one",
+      "searchOption": "Search for an option"
     }
   }
 }

+ 6 - 6
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -151,7 +151,7 @@ void _resolveGridDeps(GetIt getIt) {
     (view, _) => GridBloc(view: view, service: GridService()),
   );
 
-  getIt.registerFactoryParam<RowBloc, GridRowData, void>(
+  getIt.registerFactoryParam<RowBloc, RowData, void>(
     (data, _) => RowBloc(
       rowData: data,
       rowlistener: RowListener(rowId: data.rowId),
@@ -179,35 +179,35 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<TextCellBloc, FutureCellData, void>(
+  getIt.registerFactoryParam<TextCellBloc, CellData, void>(
     (cellData, _) => TextCellBloc(
       service: CellService(),
       cellData: cellData,
     ),
   );
 
-  getIt.registerFactoryParam<SelectionCellBloc, FutureCellData, void>(
+  getIt.registerFactoryParam<SelectionCellBloc, CellData, void>(
     (cellData, _) => SelectionCellBloc(
       service: CellService(),
       cellData: cellData,
     ),
   );
 
-  getIt.registerFactoryParam<NumberCellBloc, FutureCellData, void>(
+  getIt.registerFactoryParam<NumberCellBloc, CellData, void>(
     (cellData, _) => NumberCellBloc(
       service: CellService(),
       cellData: cellData,
     ),
   );
 
-  getIt.registerFactoryParam<DateCellBloc, FutureCellData, void>(
+  getIt.registerFactoryParam<DateCellBloc, CellData, void>(
     (cellData, _) => DateCellBloc(
       service: CellService(),
       cellData: cellData,
     ),
   );
 
-  getIt.registerFactoryParam<CheckboxCellBloc, FutureCellData, void>(
+  getIt.registerFactoryParam<CheckboxCellBloc, CellData, void>(
     (cellData, _) => CheckboxCellBloc(
       service: CellService(),
       cellData: cellData,

+ 2 - 2
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart

@@ -9,11 +9,11 @@ part 'checkbox_cell_bloc.freezed.dart';
 
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
   final CellService service;
-  // final FutureCellData cellData;
+  // final CellData cellData;
 
   CheckboxCellBloc({
     required this.service,
-    required FutureCellData cellData,
+    required CellData cellData,
   }) : super(CheckboxCellState.initial()) {
     on<CheckboxCellEvent>(
       (event, emit) async {

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart

@@ -9,7 +9,7 @@ part 'date_cell_bloc.freezed.dart';
 
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
   final CellService service;
-  final FutureCellData cellData;
+  final CellData cellData;
 
   DateCellBloc({
     required this.service,

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart

@@ -12,7 +12,7 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
 
   NumberCellBloc({
     required this.service,
-    required FutureCellData cellData,
+    required CellData cellData,
   }) : super(NumberCellState.initial()) {
     on<NumberCellEvent>(
       (event, emit) async {

+ 54 - 9
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart

@@ -1,5 +1,7 @@
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -13,12 +15,17 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
 
   SelectionCellBloc({
     required this.service,
-    required FutureCellData cellData,
-  }) : super(SelectionCellState.initial()) {
+    required CellData cellData,
+  }) : super(SelectionCellState.initial(cellData)) {
     on<SelectionCellEvent>(
       (event, emit) async {
         await event.map(
-          initial: (_InitialCell value) async {},
+          initial: (_InitialCell value) async {
+            _loadOptions();
+          },
+          didReceiveOptions: (_DidReceiveOptions value) {
+            emit(state.copyWith(options: value.options, selectedOptions: value.selectedOptions));
+          },
         );
       },
     );
@@ -28,19 +35,57 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
   Future<void> close() async {
     return super.close();
   }
+
+  void _loadOptions() async {
+    final result = await FieldContextLoaderAdaptor(
+      gridId: state.cellData.gridId,
+      field: state.cellData.field,
+    ).load();
+
+    result.fold(
+      (context) {
+        List<SelectOption> options = [];
+        switch (state.cellData.field.fieldType) {
+          case FieldType.MultiSelect:
+            options.addAll(MultiSelectTypeOption.fromBuffer(context.typeOptionData).options);
+            break;
+          case FieldType.SingleSelect:
+            options.addAll(SingleSelectTypeOption.fromBuffer(context.typeOptionData).options);
+            break;
+          default:
+            Log.error("Invalid field type, expect single select or multiple select");
+            break;
+        }
+
+        final ids = state.cellData.cell?.content.split(',');
+        final selectedOptions = ids?.map((id) => options.firstWhere((option) => option.id == id)).toList() ?? [];
+        add(SelectionCellEvent.didReceiveOptions(options, selectedOptions));
+      },
+      (err) => Log.error(err),
+    );
+  }
 }
 
 @freezed
 class SelectionCellEvent with _$SelectionCellEvent {
   const factory SelectionCellEvent.initial() = _InitialCell;
+  const factory SelectionCellEvent.didReceiveOptions(
+    List<SelectOption> options,
+    List<SelectOption> selectedOptions,
+  ) = _DidReceiveOptions;
 }
 
 @freezed
 class SelectionCellState with _$SelectionCellState {
-  const factory SelectionCellState() = _SelectionCellState;
-  // required String girdId,
-  //   required Field field,
-  //   required List<SelectOption> options,
+  const factory SelectionCellState({
+    required CellData cellData,
+    required List<SelectOption> options,
+    required List<SelectOption> selectedOptions,
+  }) = _SelectionCellState;
 
-  factory SelectionCellState.initial() => const SelectionCellState();
+  factory SelectionCellState.initial(CellData cellData) => SelectionCellState(
+        cellData: cellData,
+        options: [],
+        selectedOptions: [],
+      );
 }

+ 27 - 16
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart

@@ -11,16 +11,18 @@ import 'cell_service.dart';
 
 part 'selection_editor_bloc.freezed.dart';
 
-class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorState> {
+class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
   final CellService service = CellService();
   final FieldListener _listener;
 
-  SelectionEditorBloc({
+  SelectOptionEditorBloc({
     required String gridId,
     required Field field,
+    required List<SelectOption> options,
+    required List<SelectOption> selectedOptions,
   })  : _listener = FieldListener(fieldId: field.id),
-        super(SelectionEditorState.initial(gridId, field)) {
-    on<SelectionEditorEvent>(
+        super(SelectOptionEditorState.initial(gridId, field, options, selectedOptions)) {
+    on<SelectOptionEditorEvent>(
       (event, emit) async {
         await event.map(
           initial: (_Initial value) async {
@@ -34,6 +36,7 @@ class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorStat
           didReceiveOptions: (_DidReceiveOptions value) {
             emit(state.copyWith(options: value.options));
           },
+          newOption: (_newOption value) {},
         );
       },
     );
@@ -48,7 +51,7 @@ class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorStat
   void _startListening() {
     _listener.updateFieldNotifier.addPublishListener((result) {
       result.fold(
-        (field) => add(SelectionEditorEvent.didReceiveFieldUpdate(field)),
+        (field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)),
         (err) => Log.error(err),
       );
     });
@@ -70,7 +73,7 @@ class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorStat
             Log.error("Invalid field type, expect single select or multiple select");
             break;
         }
-        add(SelectionEditorEvent.didReceiveOptions(options));
+        add(SelectOptionEditorEvent.didReceiveOptions(options));
       },
       (err) => Log.error(err),
     );
@@ -78,25 +81,33 @@ class SelectionEditorBloc extends Bloc<SelectionEditorEvent, SelectionEditorStat
 }
 
 @freezed
-class SelectionEditorEvent with _$SelectionEditorEvent {
-  const factory SelectionEditorEvent.initial() = _Initial;
-  const factory SelectionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
-  const factory SelectionEditorEvent.didReceiveOptions(List<SelectOption> options) = _DidReceiveOptions;
+class SelectOptionEditorEvent with _$SelectOptionEditorEvent {
+  const factory SelectOptionEditorEvent.initial() = _Initial;
+  const factory SelectOptionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
+  const factory SelectOptionEditorEvent.didReceiveOptions(List<SelectOption> options) = _DidReceiveOptions;
+  const factory SelectOptionEditorEvent.newOption(String optionName) = _newOption;
 }
 
 @freezed
-class SelectionEditorState with _$SelectionEditorState {
-  const factory SelectionEditorState({
+class SelectOptionEditorState with _$SelectOptionEditorState {
+  const factory SelectOptionEditorState({
     required String gridId,
     required Field field,
     required List<SelectOption> options,
-  }) = _SelectionEditorState;
+    required List<SelectOption> selectedOptions,
+  }) = _SelectOptionEditorState;
 
-  factory SelectionEditorState.initial(String gridId, Field field) {
-    return SelectionEditorState(
+  factory SelectOptionEditorState.initial(
+    String gridId,
+    Field field,
+    List<SelectOption> options,
+    List<SelectOption> selectedOptions,
+  ) {
+    return SelectOptionEditorState(
       gridId: gridId,
       field: field,
-      options: [],
+      options: options,
+      selectedOptions: selectedOptions,
     );
   }
 }

+ 4 - 4
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart

@@ -11,7 +11,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
 
   TextCellBloc({
     required this.service,
-    required FutureCellData cellData,
+    required CellData cellData,
   }) : super(TextCellState.initial(cellData)) {
     on<TextCellEvent>(
       (event, emit) async {
@@ -53,7 +53,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
 @freezed
 class TextCellEvent with _$TextCellEvent {
   const factory TextCellEvent.initial() = _InitialCell;
-  const factory TextCellEvent.didReceiveCellData(GridCellData cellData) = _DidReceiveCellData;
+  const factory TextCellEvent.didReceiveCellData(CellData cellData) = _DidReceiveCellData;
   const factory TextCellEvent.updateText(String text) = _UpdateText;
 }
 
@@ -61,10 +61,10 @@ class TextCellEvent with _$TextCellEvent {
 class TextCellState with _$TextCellState {
   const factory TextCellState({
     required String content,
-    required FutureCellData cellData,
+    required CellData cellData,
   }) = _TextCellState;
 
-  factory TextCellState.initial(FutureCellData cellData) => TextCellState(
+  factory TextCellState.initial(CellData cellData) => TextCellState(
         content: cellData.cell?.content ?? "",
         cellData: cellData,
       );

+ 4 - 4
frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart

@@ -169,14 +169,14 @@ class GridBlockRow {
   });
 }
 
-class GridRowData extends Equatable {
+class RowData extends Equatable {
   final String gridId;
   final String rowId;
   final String blockId;
   final List<Field> fields;
   final double height;
 
-  const GridRowData({
+  const RowData({
     required this.gridId,
     required this.rowId,
     required this.blockId,
@@ -184,8 +184,8 @@ class GridRowData extends Equatable {
     required this.height,
   });
 
-  factory GridRowData.fromBlockRow(GridBlockRow row, List<Field> fields) {
-    return GridRowData(
+  factory RowData.fromBlockRow(GridBlockRow row, List<Field> fields) {
+    return RowData(
       gridId: row.gridId,
       rowId: row.rowId,
       blockId: row.blockId,

+ 4 - 4
frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart

@@ -13,14 +13,14 @@ import 'package:dartz/dartz.dart';
 
 part 'row_bloc.freezed.dart';
 
-typedef CellDataMap = LinkedHashMap<String, GridCellData>;
+typedef CellDataMap = LinkedHashMap<String, CellData>;
 
 class RowBloc extends Bloc<RowEvent, RowState> {
   final RowService rowService;
   final RowListener rowlistener;
   final GridFieldsListener fieldListener;
 
-  RowBloc({required GridRowData rowData, required this.rowlistener})
+  RowBloc({required RowData rowData, required this.rowlistener})
       : rowService = RowService(
           gridId: rowData.gridId,
           blockId: rowData.blockId,
@@ -112,7 +112,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
     var map = CellDataMap.new();
     for (final field in state.fields) {
       if (field.visibility) {
-        map[field.id] = GridCellData(
+        map[field.id] = CellData(
           rowId: row.id,
           gridId: rowService.gridId,
           blockId: rowService.blockId,
@@ -143,7 +143,7 @@ class RowState with _$RowState {
     required Option<CellDataMap> cellDataMap,
   }) = _RowState;
 
-  factory RowState.initial(GridRowData data) => RowState(
+  factory RowState.initial(RowData data) => RowState(
         rowId: data.rowId,
         rowHeight: data.height,
         fields: data.fields,

+ 2 - 4
frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart

@@ -29,16 +29,14 @@ class RowService {
   }
 }
 
-typedef FutureCellData = GridCellData;
-
-class GridCellData extends Equatable {
+class CellData extends Equatable {
   final String gridId;
   final String rowId;
   final String blockId;
   final Field field;
   final Cell? cell;
 
-  const GridCellData({
+  const CellData({
     required this.rowId,
     required this.gridId,
     required this.blockId,

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart

@@ -157,7 +157,7 @@ class _FlowyGridState extends State<FlowyGrid> {
             (context, index) {
               final blockRow = context.read<GridBloc>().state.rows[index];
               final fields = context.read<GridBloc>().state.fields;
-              final rowData = GridRowData.fromBlockRow(blockRow, fields);
+              final rowData = RowData.fromBlockRow(blockRow, fields);
               return GridRowWidget(data: rowData, key: ValueKey(rowData.rowId));
             },
             childCount: context.read<GridBloc>().state.rows.length,

+ 7 - 6
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart

@@ -9,8 +9,9 @@ class GridSize {
   static double get leadingHeaderPadding => 30 * scale;
   static double get trailHeaderPadding => 140 * scale;
   static double get headerContainerPadding => 0 * scale;
-  static double get cellContentPadding => 10 * scale;
-  static double get typeOptionItemHeight => 30 * scale;
+  static double get cellHPadding => 10 * scale;
+  static double get cellVPadding => 8 * scale;
+  static double get typeOptionItemHeight => 32 * scale;
   static double get typeOptionSeparatorHeight => 6 * scale;
 
   //
@@ -19,13 +20,13 @@ class GridSize {
         vertical: GridSize.headerContainerPadding,
       );
   static EdgeInsets get cellContentInsets => EdgeInsets.symmetric(
-        horizontal: GridSize.cellContentPadding,
-        vertical: GridSize.cellContentPadding,
+        horizontal: GridSize.cellHPadding,
+        vertical: GridSize.cellVPadding,
       );
 
   static EdgeInsets get fieldContentInsets => EdgeInsets.symmetric(
-        horizontal: GridSize.cellContentPadding,
-        vertical: GridSize.cellContentPadding,
+        horizontal: GridSize.cellHPadding,
+        vertical: GridSize.cellVPadding,
       );
 
   static EdgeInsets get typeOptionContentInsets => const EdgeInsets.symmetric(

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart

@@ -7,7 +7,7 @@ import 'number_cell.dart';
 import 'selection_cell/selection_cell.dart';
 import 'text_cell.dart';
 
-Widget buildGridCell(FutureCellData cellData) {
+Widget buildGridCell(CellData cellData) {
   final key = ValueKey(cellData.field.id + cellData.rowId);
   switch (cellData.field.fieldType) {
     case FieldType.Checkbox:

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart

@@ -38,7 +38,7 @@ class CellContainer extends StatelessWidget {
             ),
             decoration: _makeBoxDecoration(context, state),
             padding: GridSize.cellContentInsets,
-            child: Center(child: IntrinsicHeight(child: child)),
+            child: Center(child: child),
           );
         },
       ),

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart

@@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class CheckboxCell extends StatefulWidget {
-  final FutureCellData cellData;
+  final CellData cellData;
 
   const CheckboxCell({
     required this.cellData,

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell.dart

@@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class DateCell extends StatefulWidget {
-  final FutureCellData cellData;
+  final CellData cellData;
 
   const DateCell({
     required this.cellData,

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class NumberCell extends StatefulWidget {
-  final FutureCellData cellData;
+  final CellData cellData;
 
   const NumberCell({
     required this.cellData,

+ 53 - 35
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart

@@ -1,3 +1,6 @@
+import 'dart:collection';
+
+import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
@@ -61,10 +64,19 @@ extension SelectOptionColorExtension on SelectOptionColor {
 }
 
 class SelectOptionTextField extends StatelessWidget {
-  final TextEditingController _controller;
   final FocusNode _focusNode;
+  final TextEditingController _controller;
+  final TextfieldTagsController tagController;
+  final LinkedHashMap<String, SelectOption> optionMap;
+  final double distanceToText;
+
+  final Function(String) onNewTag;
 
   SelectOptionTextField({
+    required this.optionMap,
+    required this.distanceToText,
+    required this.tagController,
+    required this.onNewTag,
     TextEditingController? controller,
     FocusNode? focusNode,
     Key? key,
@@ -74,26 +86,45 @@ class SelectOptionTextField extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+
     return TextFieldTags(
       textEditingController: _controller,
-      initialTags: ["abc", "bdf"],
+      textfieldTagsController: tagController,
+      initialTags: optionMap.keys.toList(),
       focusNode: _focusNode,
       textSeparators: const [' ', ','],
       inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) {
         return ((context, sc, tags, onTagDelegate) {
+          tags.retainWhere((name) => optionMap.containsKey(name) == false);
+          if (tags.isNotEmpty) {
+            assert(tags.length == 1);
+            onNewTag(tags.first);
+          }
+
           return TextField(
             controller: editController,
             focusNode: focusNode,
             onChanged: onChanged,
             onSubmitted: onSubmitted,
-            onEditingComplete: () => focusNode.unfocus(),
             maxLines: 1,
             style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
             decoration: InputDecoration(
-              contentPadding: EdgeInsets.zero,
-              border: InputBorder.none,
+              border: OutlineInputBorder(
+                borderSide: BorderSide(color: theme.shader3, width: 1.0),
+                borderRadius: Corners.s10Border,
+              ),
               isDense: true,
-              prefixIcon: _renderTags(tags, sc),
+              prefixIcon: _renderTags(sc),
+              hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
+              prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
+              focusedBorder: OutlineInputBorder(
+                borderSide: BorderSide(
+                  color: theme.main1,
+                  width: 1.0,
+                ),
+                borderRadius: Corners.s10Border,
+              ),
             ),
           );
         });
@@ -101,41 +132,26 @@ class SelectOptionTextField extends StatelessWidget {
     );
   }
 
-  Widget? _renderTags(List<String> tags, ScrollController sc) {
-    if (tags.isEmpty) {
+  Widget? _renderTags(ScrollController sc) {
+    if (optionMap.isEmpty) {
       return null;
     }
 
-    return SingleChildScrollView(
-      controller: sc,
-      scrollDirection: Axis.horizontal,
-      child: Row(children: [
-        Container(
-          decoration: BoxDecoration(
-            color: Color.fromARGB(255, 74, 137, 92),
-            shape: BoxShape.rectangle,
-            borderRadius: BorderRadius.circular(6.0),
-          ),
-          child: FlowyText.medium("efc", fontSize: 12),
-        ),
-        Container(
-          decoration: BoxDecoration(
-            color: Color.fromARGB(255, 74, 137, 92),
-            shape: BoxShape.rectangle,
-            borderRadius: BorderRadius.circular(6.0),
-          ),
-          child: FlowyText.medium("abc", fontSize: 12),
-          margin: const EdgeInsets.symmetric(horizontal: 5.0),
-          padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
-        )
-      ]),
+    final children = optionMap.values.map((option) => SelectOptionTag(option: option)).toList();
+    return Padding(
+      padding: const EdgeInsets.all(8.0),
+      child: SingleChildScrollView(
+        controller: sc,
+        scrollDirection: Axis.horizontal,
+        child: Row(children: children),
+      ),
     );
   }
 }
 
-class SelectionBadge extends StatelessWidget {
+class SelectOptionTag extends StatelessWidget {
   final SelectOption option;
-  const SelectionBadge({required this.option, Key? key}) : super(key: key);
+  const SelectOptionTag({required this.option, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -143,9 +159,11 @@ class SelectionBadge extends StatelessWidget {
       decoration: BoxDecoration(
         color: option.color.make(context),
         shape: BoxShape.rectangle,
-        borderRadius: BorderRadius.circular(6.0),
+        borderRadius: BorderRadius.circular(8.0),
       ),
-      child: FlowyText.medium(option.name, fontSize: 12),
+      child: Center(child: FlowyText.medium(option.name, fontSize: 12)),
+      margin: const EdgeInsets.symmetric(horizontal: 3.0),
+      padding: const EdgeInsets.symmetric(horizontal: 6.0),
     );
   }
 }

+ 12 - 14
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart

@@ -6,9 +6,10 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 import 'extension.dart';
+import 'selection_editor.dart';
 
 class SingleSelectCell extends StatefulWidget {
-  final FutureCellData cellData;
+  final CellData cellData;
 
   const SingleSelectCell({
     required this.cellData,
@@ -20,30 +21,28 @@ class SingleSelectCell extends StatefulWidget {
 }
 
 class _SingleSelectCellState extends State<SingleSelectCell> {
-  late CellFocusNode _focusNode;
   late SelectionCellBloc _cellBloc;
-  late TextEditingController _controller;
 
   @override
   void initState() {
-    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellData);
-    _controller = TextEditingController();
-    _focusNode = CellFocusNode();
+    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellData)..add(const SelectionCellEvent.initial());
     super.initState();
   }
 
   @override
   Widget build(BuildContext context) {
-    _focusNode.addCallback(context, () {
-      Log.info(_focusNode.hasFocus);
-    });
     return BlocProvider.value(
       value: _cellBloc,
       child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
         builder: (context, state) {
-          return SelectOptionTextField(
-            focusNode: _focusNode,
-            controller: _controller,
+          final children = state.selectedOptions.map((option) => SelectOptionTag(option: option)).toList();
+          return SizedBox.expand(
+            child: InkWell(
+              onTap: () {
+                SelectionEditor.show(context, state.cellData, state.options, state.selectedOptions);
+              },
+              child: Row(children: children),
+            ),
           );
         },
       ),
@@ -53,14 +52,13 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
   @override
   Future<void> dispose() async {
     _cellBloc.close();
-    _focusNode.dispose();
     super.dispose();
   }
 }
 
 //----------------------------------------------------------------
 class MultiSelectCell extends StatefulWidget {
-  final FutureCellData cellData;
+  final CellData cellData;
 
   const MultiSelectCell({
     required this.cellData,

+ 124 - 38
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart

@@ -1,6 +1,9 @@
+import 'dart:collection';
+
 import 'package:app_flowy/workspace/application/grid/cell_bloc/selection_editor_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:flowy_infra/image.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';
@@ -12,42 +15,81 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
+import 'package:textfield_tags/textfield_tags.dart';
 
 import 'extension.dart';
 
+const double _editorPannelWidth = 300;
+
 class SelectionEditor extends StatelessWidget {
-  final GridCellData cellData;
-  const SelectionEditor({required this.cellData, Key? key}) : super(key: key);
+  final CellData cellData;
+  final List<SelectOption> options;
+  final List<SelectOption> selectedOptions;
 
-  void show(BuildContext context) {
-    FlowyOverlay.of(context).insertWithAnchor(
-      widget: OverlayContainer(
-        child: this,
-        constraints: BoxConstraints.loose(const Size(240, 200)),
-      ),
-      identifier: toString(),
-      anchorContext: context,
-      anchorDirection: AnchorDirection.bottomWithLeftAligned,
-    );
+  const SelectionEditor({
+    required this.cellData,
+    required this.options,
+    required this.selectedOptions,
+    Key? key,
+  }) : super(key: key);
+
+  static String identifier() {
+    return (SelectionEditor).toString();
   }
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => SelectionEditorBloc(gridId: cellData.gridId, field: cellData.field),
-      child: BlocBuilder<SelectionEditorBloc, SelectionEditorState>(
+      create: (context) => SelectOptionEditorBloc(
+        gridId: cellData.gridId,
+        field: cellData.field,
+        options: options,
+        selectedOptions: selectedOptions,
+      ),
+      child: BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
         builder: (context, state) {
-          return Column(
-            children: const [
-              _Title(),
-              VSpace(10),
-              _OptionList(),
+          return CustomScrollView(
+            shrinkWrap: true,
+            slivers: [
+              SliverToBoxAdapter(child: _TextField()),
+              const SliverToBoxAdapter(child: VSpace(10)),
+              const SliverToBoxAdapter(child: _Title()),
+              const SliverToBoxAdapter(child: _OptionList()),
             ],
           );
         },
       ),
     );
   }
+
+  static void show(
+    BuildContext context,
+    CellData cellData,
+    List<SelectOption> options,
+    List<SelectOption> selectedOptions,
+  ) {
+    SelectionEditor.hide(context);
+    final editor = SelectionEditor(
+      cellData: cellData,
+      options: options,
+      selectedOptions: selectedOptions,
+    );
+
+    //
+    FlowyOverlay.of(context).insertWithAnchor(
+      widget: OverlayContainer(
+        child: SizedBox(width: _editorPannelWidth, child: editor),
+        constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)),
+      ),
+      identifier: SelectionEditor.identifier(),
+      anchorContext: context,
+      anchorDirection: AnchorDirection.bottomWithCenterAligned,
+    );
+  }
+
+  static void hide(BuildContext context) {
+    FlowyOverlay.of(context).remove(identifier());
+  }
 }
 
 class _OptionList extends StatelessWidget {
@@ -55,9 +97,9 @@ class _OptionList extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return BlocBuilder<SelectionEditorBloc, SelectionEditorState>(
+    return BlocBuilder<SelectOptionEditorBloc, SelectOptionEditorState>(
       builder: (context, state) {
-        final cells = state.options.map((option) => _SelectionCell(option)).toList();
+        final cells = state.options.map((option) => _SelectOptionCell(option)).toList();
         final list = ListView.separated(
           shrinkWrap: true,
           controller: ScrollController(),
@@ -76,40 +118,84 @@ class _OptionList extends StatelessWidget {
   }
 }
 
+class _TextField extends StatelessWidget {
+  final TextfieldTagsController _tagController = TextfieldTagsController();
+
+  _TextField({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocConsumer<SelectOptionEditorBloc, SelectOptionEditorState>(
+      listener: (context, state) {},
+      buildWhen: (previous, current) => previous.field.id != current.field.id,
+      builder: (context, state) {
+        final optionMap = LinkedHashMap<String, SelectOption>.fromIterable(state.selectedOptions,
+            key: (option) => option.name, value: (option) => option);
+        return SizedBox(
+          height: 42,
+          child: SelectOptionTextField(
+            optionMap: optionMap,
+            distanceToText: _editorPannelWidth * 0.7,
+            tagController: _tagController,
+            onNewTag: (newTagName) {
+              context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.newOption(newTagName));
+            },
+          ),
+        );
+      },
+    );
+  }
+}
+
 class _Title extends StatelessWidget {
   const _Title({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
     return SizedBox(
       height: GridSize.typeOptionItemHeight,
-      child: FlowyText.medium(LocaleKeys.grid_selectOption_pannelTitle.tr(), fontSize: 12),
+      child: Padding(
+        padding: const EdgeInsets.symmetric(horizontal: 6),
+        child: FlowyText.medium(
+          LocaleKeys.grid_selectOption_pannelTitle.tr(),
+          fontSize: 12,
+          color: theme.shader3,
+        ),
+      ),
     );
   }
 }
 
-class _SelectionCell extends StatelessWidget {
+class _SelectOptionCell extends StatelessWidget {
   final SelectOption option;
-  const _SelectionCell(this.option, {Key? key}) : super(key: key);
+  const _SelectOptionCell(this.option, {Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
+    return SizedBox(
+      height: GridSize.typeOptionItemHeight,
+      child: InkWell(
+        onTap: () {},
+        child: FlowyHover(
+          config: HoverDisplayConfig(hoverColor: theme.hover),
+          builder: (_, onHover) {
+            List<Widget> children = [
+              SelectOptionTag(option: option),
+              const Spacer(),
+            ];
 
-    // return FlowyButton(
-    //   text: FlowyText.medium(fieldType.title(), fontSize: 12),
-    //   hoverColor: theme.hover,
-    //   onTap: () => onSelectField(fieldType),
-    //   leftIcon: svgWidget(fieldType.iconName(), color: theme.iconColor),
-    // );
-
-    return InkWell(
-      onTap: () {},
-      child: FlowyHover(
-        config: HoverDisplayConfig(hoverColor: theme.hover),
-        builder: (_, onHover) {
-          return SelectionBadge(option: option);
-        },
+            if (onHover) {
+              children.add(svgWidget("editor/details", color: theme.iconColor));
+            }
+
+            return Padding(
+              padding: const EdgeInsets.all(3.0),
+              child: Row(children: children),
+            );
+          },
+        ),
       ),
     );
   }

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart

@@ -6,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'cell_container.dart';
 
 class GridTextCell extends GridCell {
-  final FutureCellData cellData;
+  final CellData cellData;
   const GridTextCell({
     required this.cellData,
     Key? key,

+ 44 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/cell/number_cell.dart

@@ -0,0 +1,44 @@
+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';
+
+class NumberCell extends StatefulWidget {
+  final CellData cellData;
+
+  const NumberCell({
+    required this.cellData,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<NumberCell> createState() => _NumberCellState();
+}
+
+class _NumberCellState extends State<NumberCell> {
+  late NumberCellBloc _cellBloc;
+
+  @override
+  void initState() {
+    _cellBloc = getIt<NumberCellBloc>(param1: widget.cellData);
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider.value(
+      value: _cellBloc,
+      child: BlocBuilder<NumberCellBloc, NumberCellState>(
+        builder: (context, state) {
+          return Container();
+        },
+      ),
+    );
+  }
+
+  @override
+  Future<void> dispose() async {
+    _cellBloc.close();
+    super.dispose();
+  }
+}

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart

@@ -10,7 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:provider/provider.dart';
 
 class GridRowWidget extends StatefulWidget {
-  final GridRowData data;
+  final RowData data;
   const GridRowWidget({required this.data, Key? key}) : super(key: key);
 
   @override

+ 44 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/number_cell.dart

@@ -0,0 +1,44 @@
+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';
+
+class NumberCell extends StatefulWidget {
+  final CellData cellData;
+
+  const NumberCell({
+    required this.cellData,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<NumberCell> createState() => _NumberCellState();
+}
+
+class _NumberCellState extends State<NumberCell> {
+  late NumberCellBloc _cellBloc;
+
+  @override
+  void initState() {
+    _cellBloc = getIt<NumberCellBloc>(param1: widget.cellData);
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider.value(
+      value: _cellBloc,
+      child: BlocBuilder<NumberCellBloc, NumberCellState>(
+        builder: (context, state) {
+          return Container();
+        },
+      ),
+    );
+  }
+
+  @override
+  Future<void> dispose() async {
+    _cellBloc.close();
+    super.dispose();
+  }
+}

+ 1 - 1
frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart

@@ -44,7 +44,7 @@ class FlowyText extends StatelessWidget {
         softWrap: false,
         textAlign: textAlign,
         style: TextStyle(
-          color: theme.textColor,
+          color: color ?? theme.textColor,
           fontWeight: fontWeight,
           fontSize: fontSize + 2,
           fontFamily: 'Mulish',

+ 1 - 1
shared-lib/flowy-grid-data-model/src/entities/meta.rs

@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter, EnumString};
 
-pub const DEFAULT_ROW_HEIGHT: i32 = 36;
+pub const DEFAULT_ROW_HEIGHT: i32 = 42;
 pub const DEFAULT_FIELD_WIDTH: i32 = 150;
 
 #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]