Преглед изворни кода

chore: Edit Row changes (#2564)

* chore: checklist value and popup

* chore: properties side panel

* chore: reorganize checklist field

* chore: delete property promt

* chore: delete property reorganize

* fix: dnd bug of checklist field

* fix: whitespace on empty fields

* chore: new checklist item

* fix: duplicate view

* fix: named checklist bars

* chore: checklist padding

* fix: onclick

* chore: change to nullish coalescing operator

* chore: remove empty string from use translation

* fix: add missing translations

* chore: refactor select option and checklist field editors
Askarbek Zadauly пре 2 година
родитељ
комит
6935653e15
66 измењених фајлова са 1282 додато и 433 уклоњено
  1. 6 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/Button.tsx
  2. 27 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/CheckListProgress.tsx
  3. 0 156
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CellOptionsPopup.tsx
  4. 40 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/CheckList.tsx
  5. 60 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/CheckListOption.tsx
  6. 97 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/CheckListPopup.tsx
  7. 94 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/EditCheckListPopup.tsx
  8. 28 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/NewCheckListButton.tsx
  9. 53 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/NewCheckListOption.tsx
  10. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateFormatPopup.tsx
  11. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DatePickerPopup.tsx
  12. 0 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateTimeFormat.hooks.ts
  13. 5 8
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateTypeOptions.tsx
  14. 4 4
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/EditCellDate.tsx
  15. 1 4
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/NumberFormat.hooks.ts
  16. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/NumberFormatPopup.tsx
  17. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/TimeFormatPopup.tsx
  18. 30 16
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellWrapper.tsx
  19. 2 24
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx
  20. 216 122
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx
  21. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/FieldTypeName.tsx
  22. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/InlineEditFields/EditCellNumber.tsx
  23. 4 4
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/InlineEditFields/EditCellText.tsx
  24. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/InlineEditFields/EditCellUrl.tsx
  25. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/InlineEditFields/EditCheckboxCell.tsx
  26. 69 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOption.tsx
  27. 5 9
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOptions.tsx
  28. 104 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOptionsPopup.tsx
  29. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/EditCellOptionPopup.tsx
  30. 30 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/SelectedOption.tsx
  31. 169 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/PropertiesPanel.tsx
  32. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/PopupWindow.tsx
  33. 24 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/PromptWindow.tsx
  34. 8 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/Switch.tsx
  35. 12 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/DragElementSvg.tsx
  36. 9 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/ImageSvg.tsx
  37. 4 5
      frontend/appflowy_tauri/src/appflowy_app/components/auth/Login/Login.tsx
  38. 4 4
      frontend/appflowy_tauri/src/appflowy_app/components/auth/SignUp/SignUp.tsx
  39. 0 2
      frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx
  40. 4 1
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx
  41. 8 3
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCell.tsx
  42. 37 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCheckListCell.tsx
  43. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardDateCell.tsx
  44. 4 1
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroup.tsx
  45. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardOptionsCell.tsx
  46. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardSettingsPopup.tsx
  47. 2 1
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardTextCell.tsx
  48. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardUrlCell.tsx
  49. 0 14
      frontend/appflowy_tauri/src/appflowy_app/components/board/NewBoardBlock.tsx
  50. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridCheckBox.tsx
  51. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridDate.tsx
  52. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridNumberCell.tsx
  53. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridSingleSelectOptions.tsx
  54. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridTextCell.tsx
  55. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridUrl.tsx
  56. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeader.tsx
  57. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridAddRow.tsx
  58. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx
  59. 7 7
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts
  60. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.tsx
  61. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.hooks.ts
  62. 6 5
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts
  63. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/layout/Workspace.hooks.ts
  64. 8 0
      frontend/appflowy_tauri/src/appflowy_app/components/tests/AllIcons.tsx
  65. 1 0
      frontend/appflowy_tauri/src/main.tsx
  66. 58 0
      frontend/appflowy_tauri/src/styles/switch.css

+ 6 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/Button.tsx

@@ -5,7 +5,7 @@ export const Button = ({
   children,
   onClick,
 }: {
-  size?: 'primary' | 'medium' | 'small' | 'box-small-transparent';
+  size?: 'primary' | 'medium' | 'small' | 'box-small-transparent' | 'medium-transparent';
   children: ReactNode;
   onClick?: MouseEventHandler<HTMLButtonElement>;
 }) => {
@@ -21,6 +21,11 @@ export const Button = ({
       case 'small':
         setCls('w-[68px] h-[32px] flex items-center justify-center rounded-lg bg-main-accent text-white text-xs');
         break;
+      case 'medium-transparent':
+        setCls(
+          'w-[170px] h-[48px] flex items-center justify-center rounded-lg border border-main-accent text-main-accent transition-colors duration-300 hover:bg-main-hovered hover:text-white'
+        );
+        break;
       case 'box-small-transparent':
         setCls('text-black hover:text-main-accent w-[24px] h-[24px]');
         break;

+ 27 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/CheckListProgress.tsx

@@ -0,0 +1,27 @@
+export const CheckListProgress = ({ completed, max }: { completed: number; max: number }) => {
+  return (
+    <div className={'flex w-full items-center gap-4 py-1'}>
+      {max > 0 && (
+        <>
+          <div className={'flex flex-1 gap-1'}>
+            {completed > 0 && filledCheckListBars({ amount: completed })}
+            {max - completed > 0 && emptyCheckListBars({ amount: max - completed })}
+          </div>
+          <div className={'text-xs text-shade-4'}>{((100 * completed) / max).toFixed(0)}%</div>
+        </>
+      )}
+    </div>
+  );
+};
+
+const filledCheckListBars = ({ amount }: { amount: number }) => {
+  return Array(amount)
+    .fill(0)
+    .map((item, index) => <div key={index} className={'h-[4px] flex-1 flex-shrink-0 rounded bg-main-accent'}></div>);
+};
+
+const emptyCheckListBars = ({ amount }: { amount: number }) => {
+  return Array(amount)
+    .fill(0)
+    .map((item, index) => <div key={index} className={'h-[4px] flex-1 flex-shrink-0 rounded bg-tint-9'}></div>);
+};

+ 0 - 156
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CellOptionsPopup.tsx

@@ -1,156 +0,0 @@
-import { KeyboardEventHandler, useEffect, useRef, useState } from 'react';
-import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
-import { useCell } from '$app/components/_shared/database-hooks/useCell';
-import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
-import { FieldController } from '$app/stores/effects/database/field/field_controller';
-import { SelectOptionCellDataPB, SelectOptionColorPB, SelectOptionPB } from '@/services/backend';
-import { getBgColor } from '$app/components/_shared/getColor';
-import { useTranslation } from 'react-i18next';
-import { Details2Svg } from '$app/components/_shared/svg/Details2Svg';
-import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
-import { CloseSvg } from '$app/components/_shared/svg/CloseSvg';
-import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
-import { useAppSelector } from '$app/stores/store';
-import { ISelectOption, ISelectOptionType } from '$app/stores/reducers/database/slice';
-import { PopupWindow } from '$app/components/_shared/PopupWindow';
-
-export const CellOptionsPopup = ({
-  top,
-  left,
-  cellIdentifier,
-  cellCache,
-  fieldController,
-  onOutsideClick,
-  openOptionDetail,
-}: {
-  top: number;
-  left: number;
-  cellIdentifier: CellIdentifier;
-  cellCache: CellCache;
-  fieldController: FieldController;
-  onOutsideClick: () => void;
-  openOptionDetail: (_left: number, _top: number, _select_option: SelectOptionPB) => void;
-}) => {
-  const inputRef = useRef<HTMLInputElement>(null);
-  const { t } = useTranslation('');
-  const [value, setValue] = useState('');
-  const { data } = useCell(cellIdentifier, cellCache, fieldController);
-  const databaseStore = useAppSelector((state) => state.database);
-
-  useEffect(() => {
-    if (inputRef?.current) {
-      inputRef.current.focus();
-    }
-  }, [inputRef]);
-
-  const onKeyDown: KeyboardEventHandler = async (e) => {
-    if (e.key === 'Enter' && value.length > 0) {
-      await new SelectOptionCellBackendService(cellIdentifier).createOption({ name: value });
-      setValue('');
-    }
-  };
-
-  const onUnselectOptionClick = async (option: SelectOptionPB) => {
-    await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.id]);
-    setValue('');
-  };
-
-  const onToggleOptionClick = async (option: SelectOptionPB) => {
-    if ((data as SelectOptionCellDataPB)?.select_options?.find((selectedOption) => selectedOption.id === option.id)) {
-      await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.id]);
-    } else {
-      await new SelectOptionCellBackendService(cellIdentifier).selectOption([option.id]);
-    }
-    setValue('');
-  };
-
-  const onKeyDownWrapper: KeyboardEventHandler = (e) => {
-    if (e.key === 'Escape') {
-      onOutsideClick();
-    }
-  };
-
-  const onOptionDetailClick = (e: any, option: ISelectOption) => {
-    e.stopPropagation();
-    let target = e.target as HTMLElement;
-
-    while (!(target instanceof HTMLButtonElement)) {
-      if (target.parentElement === null) return;
-      target = target.parentElement;
-    }
-
-    const selectOption = new SelectOptionPB({
-      id: option.selectOptionId,
-      name: option.title,
-      color: option.color || SelectOptionColorPB.Purple,
-    });
-
-    const { right: _left, top: _top } = target.getBoundingClientRect();
-    openOptionDetail(_left, _top, selectOption);
-  };
-
-  return (
-    <PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
-      <div onKeyDown={onKeyDownWrapper} className={'flex flex-col gap-2 p-2'}>
-        <div className={'border-shades-3 flex flex-1 items-center gap-2 rounded border bg-main-selector px-2 '}>
-          <div className={'flex flex-wrap items-center gap-2 text-black'}>
-            {(data as SelectOptionCellDataPB)?.select_options?.map((option, index) => (
-              <div className={`${getBgColor(option.color)} flex items-center gap-0.5 rounded px-1 py-0.5`} key={index}>
-                <span>{option?.name || ''}</span>
-                <button onClick={() => onUnselectOptionClick(option)} className={'h-5 w-5 cursor-pointer'}>
-                  <CloseSvg></CloseSvg>{' '}
-                </button>
-              </div>
-            )) || ''}
-          </div>
-          <input
-            ref={inputRef}
-            className={'py-2'}
-            value={value}
-            onChange={(e) => setValue(e.target.value)}
-            placeholder={t('grid.selectOption.searchOption') || ''}
-            onKeyDown={onKeyDown}
-          />
-          <div className={'font-mono text-shade-3'}>{value.length}/30</div>
-        </div>
-        <div className={'-mx-4 h-[1px] bg-shade-6'}></div>
-        <div className={'font-medium text-shade-3'}>{t('grid.selectOption.panelTitle') || ''}</div>
-        <div className={'flex flex-col gap-1'}>
-          {(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType).selectOptions.map(
-            (option, index) => (
-              <div
-                key={index}
-                onClick={() =>
-                  onToggleOptionClick(
-                    new SelectOptionPB({
-                      id: option.selectOptionId,
-                      name: option.title,
-                      color: option.color || SelectOptionColorPB.Purple,
-                    })
-                  )
-                }
-                className={
-                  'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'
-                }
-              >
-                <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`}>{option.title}</div>
-                <div className={'flex items-center'}>
-                  {(data as SelectOptionCellDataPB)?.select_options?.find(
-                    (selectedOption) => selectedOption.id === option.selectOptionId
-                  ) && (
-                    <button className={'h-5 w-5 p-1'}>
-                      <CheckmarkSvg></CheckmarkSvg>
-                    </button>
-                  )}
-                  <button onClick={(e) => onOptionDetailClick(e, option)} className={'h-6 w-6 p-1'}>
-                    <Details2Svg></Details2Svg>
-                  </button>
-                </div>
-              </div>
-            )
-          )}
-        </div>
-      </div>
-    </PopupWindow>
-  );
-};

+ 40 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/CheckList.tsx

@@ -0,0 +1,40 @@
+import { SelectOptionCellDataPB } from '@/services/backend';
+import { useEffect, useRef, useState } from 'react';
+import { ISelectOptionType } from '$app_reducers/database/slice';
+import { useAppSelector } from '$app/stores/store';
+import { CheckListProgress } from '$app/components/_shared/CheckListProgress';
+
+export const CheckList = ({
+  data,
+  fieldId,
+  onEditClick,
+}: {
+  data: SelectOptionCellDataPB | undefined;
+  fieldId: string;
+  onEditClick: (left: number, top: number) => void;
+}) => {
+  const ref = useRef<HTMLDivElement>(null);
+  const [allOptionsCount, setAllOptionsCount] = useState(0);
+  const [selectedOptionsCount, setSelectedOptionsCount] = useState(0);
+  const databaseStore = useAppSelector((state) => state.database);
+
+  useEffect(() => {
+    setAllOptionsCount((databaseStore.fields[fieldId]?.fieldOptions as ISelectOptionType)?.selectOptions?.length ?? 0);
+  }, [databaseStore, fieldId]);
+
+  useEffect(() => {
+    setSelectedOptionsCount((data as SelectOptionCellDataPB)?.select_options?.length ?? 0);
+  }, [data]);
+
+  const onClick = () => {
+    if (!ref.current) return;
+    const { left, top } = ref.current.getBoundingClientRect();
+    onEditClick(left, top);
+  };
+
+  return (
+    <div ref={ref} onClick={onClick} className={'flex w-full flex-wrap items-center gap-2 px-4 py-1 text-xs text-black'}>
+      <CheckListProgress completed={selectedOptionsCount} max={allOptionsCount} />
+    </div>
+  );
+};

+ 60 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/CheckListOption.tsx

@@ -0,0 +1,60 @@
+import { SelectOptionPB } from '@/services/backend';
+import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
+import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
+import { Details2Svg } from '$app/components/_shared/svg/Details2Svg';
+import { ISelectOption } from '$app_reducers/database/slice';
+import { MouseEventHandler } from 'react';
+
+export const CheckListOption = ({
+  option,
+  checked,
+  onToggleOptionClick,
+  openCheckListDetail,
+}: {
+  option: ISelectOption;
+  checked: boolean;
+  onToggleOptionClick: (v: SelectOptionPB) => void;
+  openCheckListDetail: (left: number, top: number, option: SelectOptionPB) => void;
+}) => {
+  const onCheckListDetailClick: MouseEventHandler = (e) => {
+    e.stopPropagation();
+    let target = e.target as HTMLElement;
+
+    while (!(target instanceof HTMLButtonElement)) {
+      if (target.parentElement === null) return;
+      target = target.parentElement;
+    }
+
+    const selectOption = new SelectOptionPB({
+      id: option.selectOptionId,
+      name: option.title,
+    });
+
+    const { right: _left, top: _top } = target.getBoundingClientRect();
+    openCheckListDetail(_left, _top, selectOption);
+  };
+
+  return (
+    <div
+      className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'}
+      onClick={() =>
+        onToggleOptionClick(
+          new SelectOptionPB({
+            id: option.selectOptionId,
+            name: option.title,
+          })
+        )
+      }
+    >
+      <div className={'h-5 w-5'}>
+        {checked ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
+      </div>
+      <div className={`flex-1 px-2 py-0.5`}>{option.title}</div>
+      <div className={'flex items-center'}>
+        <button onClick={onCheckListDetailClick} className={'h-6 w-6 p-1'}>
+          <Details2Svg></Details2Svg>
+        </button>
+      </div>
+    </div>
+  );
+};

+ 97 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/CheckListPopup.tsx

@@ -0,0 +1,97 @@
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+import { SelectOptionCellDataPB, SelectOptionPB } from '@/services/backend';
+import { PopupWindow } from '$app/components/_shared/PopupWindow';
+import { ISelectOptionType } from '$app_reducers/database/slice';
+import { useAppSelector } from '$app/stores/store';
+import { useCell } from '$app/components/_shared/database-hooks/useCell';
+import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
+import { FieldController } from '$app/stores/effects/database/field/field_controller';
+import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
+import { useEffect, useState } from 'react';
+import { CheckListProgress } from '$app/components/_shared/CheckListProgress';
+import { NewCheckListOption } from '$app/components/_shared/EditRow/CheckList/NewCheckListOption';
+import { CheckListOption } from '$app/components/_shared/EditRow/CheckList/CheckListOption';
+import { NewCheckListButton } from '$app/components/_shared/EditRow/CheckList/NewCheckListButton';
+
+export const CheckListPopup = ({
+  left,
+  top,
+  cellIdentifier,
+  cellCache,
+  fieldController,
+  openCheckListDetail,
+  onOutsideClick,
+}: {
+  left: number;
+  top: number;
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+  openCheckListDetail: (left: number, top: number, option: SelectOptionPB) => void;
+  onOutsideClick: () => void;
+}) => {
+  const databaseStore = useAppSelector((state) => state.database);
+  const { data } = useCell(cellIdentifier, cellCache, fieldController);
+
+  const [allOptionsCount, setAllOptionsCount] = useState(0);
+  const [selectedOptionsCount, setSelectedOptionsCount] = useState(0);
+  const [newOptions, setNewOptions] = useState<string[]>([]);
+
+  useEffect(() => {
+    setAllOptionsCount(
+      (databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType)?.selectOptions?.length ?? 0
+    );
+  }, [databaseStore, cellIdentifier]);
+
+  useEffect(() => {
+    setSelectedOptionsCount((data as SelectOptionCellDataPB)?.select_options?.length ?? 0);
+  }, [data]);
+
+  const onToggleOptionClick = async (option: SelectOptionPB) => {
+    if ((data as SelectOptionCellDataPB)?.select_options?.find((selectedOption) => selectedOption.id === option.id)) {
+      await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.id]);
+    } else {
+      await new SelectOptionCellBackendService(cellIdentifier).selectOption([option.id]);
+    }
+  };
+
+  return (
+    <PopupWindow className={'text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
+      <div className={'min-w-[320px]'}>
+        <div className={'px-4 pt-8 pb-4'}>
+          <CheckListProgress completed={selectedOptionsCount} max={allOptionsCount} />
+        </div>
+
+        <div className={'flex flex-col p-2'}>
+          {(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType).selectOptions.map(
+            (option, index) => (
+              <CheckListOption
+                key={index}
+                option={option}
+                checked={
+                  !!(data as SelectOptionCellDataPB)?.select_options?.find((so) => so.id === option.selectOptionId)
+                }
+                onToggleOptionClick={onToggleOptionClick}
+                openCheckListDetail={openCheckListDetail}
+              ></CheckListOption>
+            )
+          )}
+          {newOptions.map((option, index) => (
+            <NewCheckListOption
+              key={index}
+              index={index}
+              option={option}
+              newOptions={newOptions}
+              setNewOptions={setNewOptions}
+              cellIdentifier={cellIdentifier}
+            ></NewCheckListOption>
+          ))}
+        </div>
+        <div className={'h-[1px] bg-shade-6'}></div>
+        <div className={'p-2'}>
+          <NewCheckListButton newOptions={newOptions} setNewOptions={setNewOptions}></NewCheckListButton>
+        </div>
+      </div>
+    </PopupWindow>
+  );
+};

+ 94 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/EditCheckListPopup.tsx

@@ -0,0 +1,94 @@
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+import { KeyboardEventHandler, useEffect, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { SelectOptionPB } from '@/services/backend';
+import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
+import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
+import { PopupWindow } from '$app/components/_shared/PopupWindow';
+
+export const EditCheckListPopup = ({
+  left,
+  top,
+  cellIdentifier,
+  editingSelectOption,
+  onOutsideClick,
+}: {
+  left: number;
+  top: number;
+  cellIdentifier: CellIdentifier;
+  editingSelectOption: SelectOptionPB;
+  onOutsideClick: () => void;
+}) => {
+  const inputRef = useRef<HTMLInputElement>(null);
+  const { t } = useTranslation();
+  const [value, setValue] = useState('');
+
+  useEffect(() => {
+    setValue(editingSelectOption.name);
+  }, [editingSelectOption]);
+
+  const onKeyDown: KeyboardEventHandler = async (e) => {
+    if (e.key === 'Enter' && value.length > 0) {
+      await new SelectOptionCellBackendService(cellIdentifier).createOption({ name: value });
+      setValue('');
+    }
+  };
+
+  const onKeyDownWrapper: KeyboardEventHandler = (e) => {
+    if (e.key === 'Escape') {
+      onOutsideClick();
+    }
+  };
+
+  const onBlur = async () => {
+    const svc = new SelectOptionCellBackendService(cellIdentifier);
+    await svc.updateOption(
+      new SelectOptionPB({
+        id: editingSelectOption.id,
+        name: value,
+      })
+    );
+  };
+
+  const onDeleteOptionClick = async () => {
+    const svc = new SelectOptionCellBackendService(cellIdentifier);
+    await svc.deleteOption([editingSelectOption]);
+  };
+
+  return (
+    <PopupWindow
+      className={'p-2 text-xs'}
+      onOutsideClick={async () => {
+        await onBlur();
+        onOutsideClick();
+      }}
+      left={left}
+      top={top}
+    >
+      <div onKeyDown={onKeyDownWrapper} className={'flex flex-col gap-2 p-2'}>
+        <div className={'border-shades-3 flex flex-1 items-center gap-2 rounded border bg-main-selector px-2 '}>
+          <input
+            ref={inputRef}
+            className={'py-2'}
+            value={value}
+            onChange={(e) => setValue(e.target.value)}
+            onKeyDown={onKeyDown}
+            onBlur={() => onBlur()}
+          />
+          <div className={'font-mono text-shade-3'}>{value.length}/30</div>
+        </div>
+        <button
+          onClick={() => onDeleteOptionClick()}
+          className={
+            'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 text-main-alert hover:bg-main-secondary'
+          }
+        >
+          <i className={'h-5 w-5'}>
+            <TrashSvg></TrashSvg>
+          </i>
+          <span>{t('grid.selectOption.deleteTag')}</span>
+        </button>
+      </div>
+    </PopupWindow>
+  );
+};

+ 28 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/NewCheckListButton.tsx

@@ -0,0 +1,28 @@
+import AddSvg from '$app/components/_shared/svg/AddSvg';
+import { useTranslation } from 'react-i18next';
+
+export const NewCheckListButton = ({
+  newOptions,
+  setNewOptions,
+}: {
+  newOptions: string[];
+  setNewOptions: (v: string[]) => void;
+}) => {
+  const { t } = useTranslation();
+
+  const newOptionClick = () => {
+    setNewOptions([...newOptions, '']);
+  };
+
+  return (
+    <button
+      onClick={() => newOptionClick()}
+      className={'flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-shade-6'}
+    >
+      <i className={'h-5 w-5'}>
+        <AddSvg></AddSvg>
+      </i>
+      <span>{t('grid.field.addOption')}</span>
+    </button>
+  );
+};

+ 53 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CheckList/NewCheckListOption.tsx

@@ -0,0 +1,53 @@
+import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
+import { useTranslation } from 'react-i18next';
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+
+export const NewCheckListOption = ({
+  index,
+  option,
+  newOptions,
+  setNewOptions,
+  cellIdentifier,
+}: {
+  index: number;
+  option: string;
+  newOptions: string[];
+  setNewOptions: (v: string[]) => void;
+  cellIdentifier: CellIdentifier;
+}) => {
+  const { t } = useTranslation();
+
+  const updateNewOption = (value: string) => {
+    const newOptionsCopy = [...newOptions];
+    newOptionsCopy[index] = value;
+    setNewOptions(newOptionsCopy);
+  };
+
+  const onNewOptionKeyDown = (e: KeyboardEvent) => {
+    if (e.key === 'Enter') {
+      void onSaveNewOptionClick();
+    }
+  };
+
+  const onSaveNewOptionClick = async () => {
+    await new SelectOptionCellBackendService(cellIdentifier).createOption({ name: newOptions[index] });
+    setNewOptions(newOptions.filter((_, i) => i !== index));
+  };
+
+  return (
+    <div className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-shade-6'}>
+      <input
+        onKeyDown={(e) => onNewOptionKeyDown(e as unknown as KeyboardEvent)}
+        className={'min-w-0 flex-1 pl-7'}
+        value={option}
+        onChange={(e) => updateNewOption(e.target.value)}
+      />
+      <button
+        onClick={() => onSaveNewOptionClick()}
+        className={'flex items-center gap-2 rounded-lg bg-main-accent px-4 py-2 text-white hover:bg-main-hovered'}
+      >
+        {t('grid.selectOption.create')}
+      </button>
+    </div>
+  );
+};

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/DateFormatPopup.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateFormatPopup.tsx

@@ -4,10 +4,10 @@ import { PopupWindow } from '$app/components/_shared/PopupWindow';
 import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
 import { useTranslation } from 'react-i18next';
 import { DateFormatPB } from '@/services/backend';
-import { useDateTimeFormat } from '$app/components/_shared/EditRow/DateTimeFormat.hooks';
+import { useDateTimeFormat } from '$app/components/_shared/EditRow/Date/DateTimeFormat.hooks';
 import { useAppSelector } from '$app/stores/store';
 import { useEffect, useState } from 'react';
-import { IDateType } from '$app/stores/reducers/database/slice';
+import { IDateType } from '$app_reducers/database/slice';
 
 export const DateFormatPopup = ({
   left,
@@ -22,7 +22,7 @@ export const DateFormatPopup = ({
   fieldController: FieldController;
   onOutsideClick: () => void;
 }) => {
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
   const { changeDateFormat } = useDateTimeFormat(cellIdentifier, fieldController);
   const databaseStore = useAppSelector((state) => state.database);
   const [dateType, setDateType] = useState<IDateType | undefined>();

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/DatePickerPopup.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DatePickerPopup.tsx

@@ -8,7 +8,7 @@ import { useCell } from '$app/components/_shared/database-hooks/useCell';
 import { CalendarData } from '$app/stores/effects/database/cell/controller_builder';
 import { DateCellDataPB } from '@/services/backend';
 import { PopupWindow } from '$app/components/_shared/PopupWindow';
-import { DateTypeOptions } from '$app/components/_shared/EditRow/DateTypeOptions';
+import { DateTypeOptions } from '$app/components/_shared/EditRow/Date/DateTypeOptions';
 
 export const DatePickerPopup = ({
   left,

+ 0 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/DateTimeFormat.hooks.ts → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateTimeFormat.hooks.ts


+ 5 - 8
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/DateTypeOptions.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/DateTypeOptions.tsx

@@ -1,13 +1,13 @@
-import { DateFormatPopup } from '$app/components/_shared/EditRow/DateFormatPopup';
-import { TimeFormatPopup } from '$app/components/_shared/EditRow/TimeFormatPopup';
+import { DateFormatPopup } from '$app/components/_shared/EditRow/Date/DateFormatPopup';
+import { TimeFormatPopup } from '$app/components/_shared/EditRow/Date/TimeFormatPopup';
 import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
 import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
 import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
 import { MouseEventHandler, useEffect, useState } from 'react';
 import { useTranslation } from 'react-i18next';
-import { IDateType } from '$app/stores/reducers/database/slice';
+import { IDateType } from '$app_reducers/database/slice';
 import { useAppSelector } from '$app/stores/store';
-import { useDateTimeFormat } from '$app/components/_shared/EditRow/DateTimeFormat.hooks';
+import { useDateTimeFormat } from '$app/components/_shared/EditRow/Date/DateTimeFormat.hooks';
 import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
 import { FieldController } from '$app/stores/effects/database/field/field_controller';
 
@@ -18,7 +18,7 @@ export const DateTypeOptions = ({
   cellIdentifier: CellIdentifier;
   fieldController: FieldController;
 }) => {
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
 
   const [showDateFormatPopup, setShowDateFormatPopup] = useState(false);
   const [dateFormatTop, setDateFormatTop] = useState(0);
@@ -105,9 +105,6 @@ export const DateTypeOptions = ({
         }
       >
         <div className={'flex items-center gap-2'}>
-          {/*<i className={'h-4 w-4'}>
-            <ClockSvg></ClockSvg>
-          </i>*/}
           <span>{t('grid.field.includeTime')}</span>
         </div>
         {/*<i className={'h-5 w-5'}>*/}

+ 4 - 4
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellDate.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/EditCellDate.tsx

@@ -1,4 +1,4 @@
-import { useRef } from 'react';
+import { MouseEventHandler, useRef } from 'react';
 import { DateCellDataPB } from '@/services/backend';
 
 export const EditCellDate = ({
@@ -10,15 +10,15 @@ export const EditCellDate = ({
 }) => {
   const ref = useRef<HTMLDivElement>(null);
 
-  const onClick = () => {
+  const onClick: MouseEventHandler = () => {
     if (!ref.current) return;
     const { left, top } = ref.current.getBoundingClientRect();
     onEditClick(left, top);
   };
 
   return (
-    <div ref={ref} onClick={() => onClick()} className={'w-full px-4 py-2'}>
-      {data?.date || <>&nbsp;</>}
+    <div ref={ref} onClick={onClick} className={'w-full px-4 py-1'}>
+      {data?.date}&nbsp;
     </div>
   );
 };

+ 1 - 4
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/NumberFormat.hooks.ts → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/NumberFormat.hooks.ts

@@ -3,10 +3,7 @@ import { FieldController } from '$app/stores/effects/database/field/field_contro
 import { FieldType, NumberFormatPB } from '@/services/backend';
 import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
 import { Some } from 'ts-results';
-import {
-  makeDateTypeOptionContext,
-  makeNumberTypeOptionContext,
-} from '$app/stores/effects/database/field/type_option/type_option_context';
+import { makeNumberTypeOptionContext } from '$app/stores/effects/database/field/type_option/type_option_context';
 
 export const useNumberFormat = (cellIdentifier: CellIdentifier, fieldController: FieldController) => {
   const changeNumberFormat = async (format: NumberFormatPB) => {

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/NumberFormatPopup.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/NumberFormatPopup.tsx

@@ -1,12 +1,12 @@
 import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
 import { FieldController } from '$app/stores/effects/database/field/field_controller';
 import { PopupWindow } from '$app/components/_shared/PopupWindow';
-import { useNumberFormat } from '$app/components/_shared/EditRow/NumberFormat.hooks';
+import { useNumberFormat } from '$app/components/_shared/EditRow/Date/NumberFormat.hooks';
 import { NumberFormatPB } from '@/services/backend';
 import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
 import { useAppSelector } from '$app/stores/store';
 import { useEffect, useState } from 'react';
-import { INumberType } from '$app/stores/reducers/database/slice';
+import { INumberType } from '$app_reducers/database/slice';
 
 const list = [
   { format: NumberFormatPB.Num, title: 'Num' },

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/TimeFormatPopup.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Date/TimeFormatPopup.tsx

@@ -4,10 +4,10 @@ import { useTranslation } from 'react-i18next';
 import { PopupWindow } from '$app/components/_shared/PopupWindow';
 import { TimeFormatPB } from '@/services/backend';
 import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
-import { useDateTimeFormat } from '$app/components/_shared/EditRow/DateTimeFormat.hooks';
+import { useDateTimeFormat } from '$app/components/_shared/EditRow/Date/DateTimeFormat.hooks';
 import { useAppSelector } from '$app/stores/store';
 import { useEffect, useState } from 'react';
-import { IDateType } from '$app/stores/reducers/database/slice';
+import { IDateType } from '$app_reducers/database/slice';
 
 export const TimeFormatPopup = ({
   left,
@@ -22,7 +22,7 @@ export const TimeFormatPopup = ({
   fieldController: FieldController;
   onOutsideClick: () => void;
 }) => {
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
   const databaseStore = useAppSelector((state) => state.database);
   const [dateType, setDateType] = useState<IDateType | undefined>();
 

+ 30 - 16
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellWrapper.tsx

@@ -4,15 +4,17 @@ import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
 import { FieldController } from '$app/stores/effects/database/field/field_controller';
 import { DateCellDataPB, FieldType, SelectOptionCellDataPB, URLCellDataPB } from '@/services/backend';
 import { useAppSelector } from '$app/stores/store';
-import { EditCellText } from '$app/components/_shared/EditRow/EditCellText';
+import { EditCellText } from '$app/components/_shared/EditRow/InlineEditFields/EditCellText';
 import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
-import { EditCellDate } from '$app/components/_shared/EditRow/EditCellDate';
+import { EditCellDate } from '$app/components/_shared/EditRow/Date/EditCellDate';
 import { useRef } from 'react';
-import { CellOptions } from '$app/components/_shared/EditRow/CellOptions';
-import { EditCellNumber } from '$app/components/_shared/EditRow/EditCellNumber';
-import { EditCheckboxCell } from '$app/components/_shared/EditRow/EditCheckboxCell';
-import { EditCellUrl } from '$app/components/_shared/EditRow/EditCellUrl';
+import { CellOptions } from '$app/components/_shared/EditRow/Options/CellOptions';
+import { EditCellNumber } from '$app/components/_shared/EditRow/InlineEditFields/EditCellNumber';
+import { EditCheckboxCell } from '$app/components/_shared/EditRow/InlineEditFields/EditCheckboxCell';
+import { EditCellUrl } from '$app/components/_shared/EditRow/InlineEditFields/EditCellUrl';
 import { Draggable } from 'react-beautiful-dnd';
+import { DragElementSvg } from '$app/components/_shared/svg/DragElementSvg';
+import { CheckList } from '$app/components/_shared/EditRow/CheckList/CheckList';
 
 export const EditCellWrapper = ({
   index,
@@ -22,6 +24,7 @@ export const EditCellWrapper = ({
   onEditFieldClick,
   onEditOptionsClick,
   onEditDateClick,
+  onEditCheckListClick,
 }: {
   index: number;
   cellIdentifier: CellIdentifier;
@@ -30,6 +33,7 @@ export const EditCellWrapper = ({
   onEditFieldClick: (cell: CellIdentifier, left: number, top: number) => void;
   onEditOptionsClick: (cell: CellIdentifier, left: number, top: number) => void;
   onEditDateClick: (cell: CellIdentifier, left: number, top: number) => void;
+  onEditCheckListClick: (cell: CellIdentifier, left: number, top: number) => void;
 }) => {
   const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
   const databaseStore = useAppSelector((state) => state.database);
@@ -48,26 +52,28 @@ export const EditCellWrapper = ({
           ref={provided.innerRef}
           {...provided.draggableProps}
           {...provided.dragHandleProps}
-          className={'flex w-full items-center text-xs'}
+          className={'flex w-full flex-col items-start gap-2 text-xs'}
         >
           <div
-            ref={el}
-            onClick={() => onClick()}
             className={
-              'relative flex w-[180px] cursor-pointer items-center gap-2 rounded-lg px-3 py-1.5 hover:bg-shade-6'
+              'relative flex cursor-pointer items-center gap-2 rounded-lg text-white transition-colors duration-200 hover:text-shade-3'
             }
           >
-            <div className={'flex h-5 w-5 flex-shrink-0 items-center justify-center'}>
+            <div ref={el} onClick={() => onClick()} className={'flex h-5 w-5'}>
+              <DragElementSvg></DragElementSvg>
+            </div>
+
+            <div className={'flex h-5 w-5 flex-shrink-0 items-center justify-center text-shade-3'}>
               <FieldTypeIcon fieldType={cellIdentifier.fieldType}></FieldTypeIcon>
             </div>
-            <span className={'overflow-hidden text-ellipsis whitespace-nowrap'}>
-              {databaseStore.fields[cellIdentifier.fieldId]?.title || ''}
+            <span className={'overflow-hidden text-ellipsis whitespace-nowrap text-shade-3'}>
+              {databaseStore.fields[cellIdentifier.fieldId]?.title ?? ''}
             </span>
           </div>
-          <div className={'flex-1 cursor-pointer rounded-lg hover:bg-shade-6'}>
+
+          <div className={'w-full cursor-pointer rounded-lg pl-3 text-sm hover:bg-shade-6'}>
             {(cellIdentifier.fieldType === FieldType.SingleSelect ||
-              cellIdentifier.fieldType === FieldType.MultiSelect ||
-              cellIdentifier.fieldType === FieldType.Checklist) &&
+              cellIdentifier.fieldType === FieldType.MultiSelect) &&
               cellController && (
                 <CellOptions
                   data={data as SelectOptionCellDataPB}
@@ -75,6 +81,14 @@ export const EditCellWrapper = ({
                 ></CellOptions>
               )}
 
+            {cellIdentifier.fieldType === FieldType.Checklist && cellController && (
+              <CheckList
+                data={data as SelectOptionCellDataPB}
+                fieldId={cellIdentifier.fieldId}
+                onEditClick={(left, top) => onEditCheckListClick(cellIdentifier, left, top)}
+              ></CheckList>
+            )}
+
             {cellIdentifier.fieldType === FieldType.Checkbox && cellController && (
               <EditCheckboxCell
                 data={data as 'Yes' | 'No' | undefined}

+ 2 - 24
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx

@@ -1,5 +1,4 @@
 import { MouseEventHandler, useEffect, useRef, useState } from 'react';
-import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
 import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
 import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
 import { useTranslation } from 'react-i18next';
@@ -11,7 +10,7 @@ import { useAppSelector } from '$app/stores/store';
 import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
 import { PopupWindow } from '$app/components/_shared/PopupWindow';
 import { FieldType } from '@/services/backend';
-import { DateTypeOptions } from '$app/components/_shared/EditRow/DateTypeOptions';
+import { DateTypeOptions } from '$app/components/_shared/EditRow/Date/DateTypeOptions';
 
 export const EditFieldPopup = ({
   top,
@@ -35,7 +34,7 @@ export const EditFieldPopup = ({
   onNumberFormat?: (buttonLeft: number, buttonTop: number) => void;
 }) => {
   const databaseStore = useAppSelector((state) => state.database);
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
   const changeTypeButtonRef = useRef<HTMLDivElement>(null);
   const [name, setName] = useState('');
 
@@ -56,15 +55,6 @@ export const EditFieldPopup = ({
     changeFieldTypeClick(buttonTop, buttonRight);
   };
 
-  // this is causing an error right now
-  const onDeleteFieldClick = async () => {
-    if (!fieldInfo) return;
-    const controller = new TypeOptionController(viewId, Some(fieldInfo));
-    await controller.initialize();
-    await controller.deleteField();
-    onOutsideClick();
-  };
-
   const onNumberFormatClick: MouseEventHandler = (e) => {
     e.stopPropagation();
     let target = e.target as HTMLElement;
@@ -96,18 +86,6 @@ export const EditFieldPopup = ({
           className={'border-shades-3 flex-1 rounded border bg-main-selector px-2 py-2'}
         />
 
-        <button
-          onClick={() => onDeleteFieldClick()}
-          className={'flex cursor-pointer items-center gap-2 rounded-lg py-2 text-main-alert hover:bg-main-secondary'}
-        >
-          <span className={'flex items-center gap-2 pl-2'}>
-            <i className={'block h-5 w-5'}>
-              <TrashSvg></TrashSvg>
-            </i>
-            <span>{t('grid.field.delete')}</span>
-          </span>
-        </button>
-
         <div
           ref={changeTypeButtonRef}
           onClick={() => onChangeFieldTypeClick()}

+ 216 - 122
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx

@@ -12,11 +12,16 @@ import { ChangeFieldTypePopup } from '$app/components/_shared/EditRow/ChangeFiel
 import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
 import { Some } from 'ts-results';
 import { FieldType, SelectOptionPB } from '@/services/backend';
-import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPopup';
-import { DatePickerPopup } from '$app/components/_shared/EditRow/DatePickerPopup';
+import { CellOptionsPopup } from '$app/components/_shared/EditRow/Options/CellOptionsPopup';
+import { DatePickerPopup } from '$app/components/_shared/EditRow/Date/DatePickerPopup';
 import { DragDropContext, Droppable, OnDragEndResponder } from 'react-beautiful-dnd';
-import { EditCellOptionPopup } from '$app/components/_shared/EditRow/EditCellOptionPopup';
-import { NumberFormatPopup } from '$app/components/_shared/EditRow/NumberFormatPopup';
+import { EditCellOptionPopup } from '$app/components/_shared/EditRow/Options/EditCellOptionPopup';
+import { NumberFormatPopup } from '$app/components/_shared/EditRow/Date/NumberFormatPopup';
+import { CheckListPopup } from '$app/components/_shared/EditRow/CheckList/CheckListPopup';
+import { EditCheckListPopup } from '$app/components/_shared/EditRow/CheckList/EditCheckListPopup';
+import { PropertiesPanel } from '$app/components/_shared/EditRow/PropertiesPanel';
+import { ImageSvg } from '$app/components/_shared/svg/ImageSvg';
+import { PromptWindow } from '$app/components/_shared/PromptWindow';
 
 export const EditRow = ({
   onClose,
@@ -30,7 +35,7 @@ export const EditRow = ({
   rowInfo: RowInfo;
 }) => {
   const { cells, onNewColumnClick } = useRow(viewId, controller, rowInfo);
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
   const [unveil, setUnveil] = useState(false);
 
   const [editingCell, setEditingCell] = useState<CellIdentifier | null>(null);
@@ -56,10 +61,21 @@ export const EditRow = ({
 
   const [editingSelectOption, setEditingSelectOption] = useState<SelectOptionPB | undefined>();
 
+  const [showEditCheckList, setShowEditCheckList] = useState(false);
+  const [editCheckListTop, setEditCheckListTop] = useState(0);
+  const [editCheckListLeft, setEditCheckListLeft] = useState(0);
+
   const [showNumberFormatPopup, setShowNumberFormatPopup] = useState(false);
   const [numberFormatTop, setNumberFormatTop] = useState(0);
   const [numberFormatLeft, setNumberFormatLeft] = useState(0);
 
+  const [showCheckListPopup, setShowCheckListPopup] = useState(false);
+  const [checkListPopupTop, setCheckListPopupTop] = useState(0);
+  const [checkListPopupLeft, setCheckListPopupLeft] = useState(0);
+
+  const [deletingPropertyId, setDeletingPropertyId] = useState<string | null>(null);
+  const [showDeletePropertyPrompt, setShowDeletePropertyPrompt] = useState(false);
+
   useEffect(() => {
     setUnveil(true);
   }, []);
@@ -125,12 +141,26 @@ export const EditRow = ({
     setEditCellOptionTop(_top);
   };
 
+  const onOpenCheckListDetailClick = (_left: number, _top: number, _select_option: SelectOptionPB) => {
+    setEditingSelectOption(_select_option);
+    setShowEditCheckList(true);
+    setEditCheckListLeft(_left + 10);
+    setEditCheckListTop(_top);
+  };
+
   const onNumberFormat = (_left: number, _top: number) => {
     setShowNumberFormatPopup(true);
     setNumberFormatLeft(_left + 10);
     setNumberFormatTop(_top);
   };
 
+  const onEditCheckListClick = (cellIdentifier: CellIdentifier, left: number, top: number) => {
+    setEditingCell(cellIdentifier);
+    setShowCheckListPopup(true);
+    setCheckListPopupLeft(left);
+    setCheckListPopupTop(top + 40);
+  };
+
   const onDragEnd: OnDragEndResponder = (result) => {
     if (!result.destination?.index) return;
     void controller.moveField({
@@ -140,129 +170,193 @@ export const EditRow = ({
     });
   };
 
+  const onDeletePropertyClick = (fieldId: string) => {
+    setDeletingPropertyId(fieldId);
+    setShowDeletePropertyPrompt(true);
+  };
+
+  const onDelete = async () => {
+    if (!deletingPropertyId) return;
+    const fieldInfo = controller.fieldController.getField(deletingPropertyId);
+    if (!fieldInfo) return;
+    const typeController = new TypeOptionController(viewId, Some(fieldInfo));
+    await typeController.initialize();
+    await typeController.deleteField();
+    setShowDeletePropertyPrompt(false);
+  };
+
   return (
-    <div
-      className={`fixed inset-0 z-10 flex items-center justify-center bg-black/30 backdrop-blur-sm transition-opacity duration-300 ${
-        unveil ? 'opacity-100' : 'opacity-0'
-      }`}
-      onClick={() => onCloseClick()}
-    >
+    <>
       <div
-        onClick={(e) => {
-          e.stopPropagation();
-        }}
-        className={`relative flex h-[90%] w-[70%] flex-col gap-8 rounded-xl bg-white px-8 pb-4 pt-12`}
+        className={`fixed inset-0 z-10 flex items-center justify-center bg-black/30 backdrop-blur-sm transition-opacity duration-300 ${
+          unveil ? 'opacity-100' : 'opacity-0'
+        }`}
+        onClick={() => onCloseClick()}
       >
-        <div onClick={() => onCloseClick()} className={'absolute top-4 right-4'}>
-          <button className={'block h-8 w-8 rounded-lg text-shade-2 hover:bg-main-secondary'}>
-            <CloseSvg></CloseSvg>
-          </button>
-        </div>
+        <div
+          onClick={(e) => {
+            e.stopPropagation();
+          }}
+          className={`relative flex h-[90%] w-[70%] flex-col gap-8 rounded-xl bg-white `}
+        >
+          <div onClick={() => onCloseClick()} className={'absolute top-1 right-1'}>
+            <button className={'block h-8 w-8 rounded-lg text-shade-2 hover:bg-main-secondary'}>
+              <CloseSvg></CloseSvg>
+            </button>
+          </div>
 
-        <DragDropContext onDragEnd={onDragEnd}>
-          <Droppable droppableId={'field-list'}>
-            {(provided) => (
-              <div
-                {...provided.droppableProps}
-                ref={provided.innerRef}
-                className={`flex flex-1 flex-col gap-2 ${
-                  showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto'
-                }`}
-              >
-                {cells.map((cell, cellIndex) => (
-                  <EditCellWrapper
-                    index={cellIndex}
-                    key={cellIndex}
-                    cellIdentifier={cell.cellIdentifier}
-                    cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
-                    fieldController={controller.fieldController}
-                    onEditFieldClick={onEditFieldClick}
-                    onEditOptionsClick={onEditOptionsClick}
-                    onEditDateClick={onEditDateClick}
-                  ></EditCellWrapper>
-                ))}
+          <div className={'flex h-full'}>
+            <div className={'flex h-full flex-1 flex-col border-r border-shade-6 pb-4 pt-6'}>
+              <div className={'pl-12 pb-4'}>
+                <button className={'flex items-center gap-2 p-4'}>
+                  <i className={'h-5 w-5'}>
+                    <ImageSvg></ImageSvg>
+                  </i>
+                  <span className={'text-xs'}>Add Cover</span>
+                </button>
               </div>
-            )}
-          </Droppable>
-        </DragDropContext>
-
-        <div className={'border-t border-shade-6 pt-2'}>
-          <button
-            onClick={() => onNewColumnClick()}
-            className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-shade-6'}
-          >
-            <i className={'h-5 w-5'}>
-              <AddSvg></AddSvg>
-            </i>
-            <span>{t('grid.field.newColumn')}</span>
-          </button>
-        </div>
 
-        {showFieldEditor && editingCell && (
-          <EditFieldPopup
-            top={editFieldTop}
-            left={editFieldLeft}
-            cellIdentifier={editingCell}
-            viewId={viewId}
-            onOutsideClick={onOutsideEditFieldClick}
-            fieldInfo={controller.fieldController.getField(editingCell.fieldId)}
-            fieldController={controller.fieldController}
-            changeFieldTypeClick={onChangeFieldTypeClick}
-            onNumberFormat={onNumberFormat}
-          ></EditFieldPopup>
-        )}
-        {showChangeFieldTypePopup && (
-          <ChangeFieldTypePopup
-            top={changeFieldTypeTop}
-            left={changeFieldTypeLeft}
-            onClick={(newType) => changeFieldType(newType)}
-            onOutsideClick={() => setShowChangeFieldTypePopup(false)}
-          ></ChangeFieldTypePopup>
-        )}
-        {showChangeOptionsPopup && editingCell && (
-          <CellOptionsPopup
-            top={changeOptionsTop}
-            left={changeOptionsLeft}
-            cellIdentifier={editingCell}
-            cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
-            fieldController={controller.fieldController}
-            onOutsideClick={() => setShowChangeOptionsPopup(false)}
-            openOptionDetail={onOpenOptionDetailClick}
-          ></CellOptionsPopup>
-        )}
-        {showDatePicker && editingCell && (
-          <DatePickerPopup
-            top={datePickerTop}
-            left={datePickerLeft}
-            cellIdentifier={editingCell}
-            cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
-            fieldController={controller.fieldController}
-            onOutsideClick={() => setShowDatePicker(false)}
-          ></DatePickerPopup>
-        )}
-        {showEditCellOption && editingCell && editingSelectOption && (
-          <EditCellOptionPopup
-            top={editCellOptionTop}
-            left={editCellOptionLeft}
-            cellIdentifier={editingCell}
-            editingSelectOption={editingSelectOption}
-            onOutsideClick={() => {
-              setShowEditCellOption(false);
-            }}
-          ></EditCellOptionPopup>
-        )}
-        {showNumberFormatPopup && editingCell && (
-          <NumberFormatPopup
-            top={numberFormatTop}
-            left={numberFormatLeft}
-            cellIdentifier={editingCell}
-            fieldController={controller.fieldController}
-            onOutsideClick={() => {
-              setShowNumberFormatPopup(false);
-            }}
-          ></NumberFormatPopup>
-        )}
+              <DragDropContext onDragEnd={onDragEnd}>
+                <Droppable droppableId={'field-list'}>
+                  {(provided) => (
+                    <div
+                      {...provided.droppableProps}
+                      ref={provided.innerRef}
+                      className={`flex flex-1 flex-col gap-8 px-8 ${
+                        showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto'
+                      }`}
+                    >
+                      {cells.map((cell, cellIndex) => (
+                        <EditCellWrapper
+                          index={cellIndex}
+                          key={cellIndex}
+                          cellIdentifier={cell.cellIdentifier}
+                          cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
+                          fieldController={controller.fieldController}
+                          onEditFieldClick={onEditFieldClick}
+                          onEditOptionsClick={onEditOptionsClick}
+                          onEditDateClick={onEditDateClick}
+                          onEditCheckListClick={onEditCheckListClick}
+                        ></EditCellWrapper>
+                      ))}
+                    </div>
+                  )}
+                </Droppable>
+              </DragDropContext>
+
+              <div className={'border-t border-shade-6 px-8 pt-2'}>
+                <button
+                  onClick={() => onNewColumnClick()}
+                  className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-shade-6'}
+                >
+                  <i className={'h-5 w-5'}>
+                    <AddSvg></AddSvg>
+                  </i>
+                  <span>{t('grid.field.newProperty')}</span>
+                </button>
+              </div>
+            </div>
+            <PropertiesPanel
+              viewId={viewId}
+              controller={controller}
+              rowInfo={rowInfo}
+              onDeletePropertyClick={onDeletePropertyClick}
+            ></PropertiesPanel>
+          </div>
+
+          {showFieldEditor && editingCell && (
+            <EditFieldPopup
+              top={editFieldTop}
+              left={editFieldLeft}
+              cellIdentifier={editingCell}
+              viewId={viewId}
+              onOutsideClick={onOutsideEditFieldClick}
+              fieldInfo={controller.fieldController.getField(editingCell.fieldId)}
+              fieldController={controller.fieldController}
+              changeFieldTypeClick={onChangeFieldTypeClick}
+              onNumberFormat={onNumberFormat}
+            ></EditFieldPopup>
+          )}
+          {showChangeFieldTypePopup && (
+            <ChangeFieldTypePopup
+              top={changeFieldTypeTop}
+              left={changeFieldTypeLeft}
+              onClick={(newType) => changeFieldType(newType)}
+              onOutsideClick={() => setShowChangeFieldTypePopup(false)}
+            ></ChangeFieldTypePopup>
+          )}
+          {showChangeOptionsPopup && editingCell && (
+            <CellOptionsPopup
+              top={changeOptionsTop}
+              left={changeOptionsLeft}
+              cellIdentifier={editingCell}
+              cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
+              fieldController={controller.fieldController}
+              onOutsideClick={() => setShowChangeOptionsPopup(false)}
+              openOptionDetail={onOpenOptionDetailClick}
+            ></CellOptionsPopup>
+          )}
+          {showDatePicker && editingCell && (
+            <DatePickerPopup
+              top={datePickerTop}
+              left={datePickerLeft}
+              cellIdentifier={editingCell}
+              cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
+              fieldController={controller.fieldController}
+              onOutsideClick={() => setShowDatePicker(false)}
+            ></DatePickerPopup>
+          )}
+          {showEditCellOption && editingCell && editingSelectOption && (
+            <EditCellOptionPopup
+              top={editCellOptionTop}
+              left={editCellOptionLeft}
+              cellIdentifier={editingCell}
+              editingSelectOption={editingSelectOption}
+              onOutsideClick={() => {
+                setShowEditCellOption(false);
+              }}
+            ></EditCellOptionPopup>
+          )}
+          {showNumberFormatPopup && editingCell && (
+            <NumberFormatPopup
+              top={numberFormatTop}
+              left={numberFormatLeft}
+              cellIdentifier={editingCell}
+              fieldController={controller.fieldController}
+              onOutsideClick={() => {
+                setShowNumberFormatPopup(false);
+              }}
+            ></NumberFormatPopup>
+          )}
+          {showCheckListPopup && editingCell && (
+            <CheckListPopup
+              top={checkListPopupTop}
+              left={checkListPopupLeft}
+              cellIdentifier={editingCell}
+              cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
+              fieldController={controller.fieldController}
+              onOutsideClick={() => setShowCheckListPopup(false)}
+              openCheckListDetail={onOpenCheckListDetailClick}
+            ></CheckListPopup>
+          )}
+          {showEditCheckList && editingCell && editingSelectOption && (
+            <EditCheckListPopup
+              top={editCheckListTop}
+              left={editCheckListLeft}
+              cellIdentifier={editingCell}
+              editingSelectOption={editingSelectOption}
+              onOutsideClick={() => setShowEditCheckList(false)}
+            ></EditCheckListPopup>
+          )}
+        </div>
       </div>
-    </div>
+      {showDeletePropertyPrompt && (
+        <PromptWindow
+          msg={'Are you sure you want to delete this property?'}
+          onYes={() => onDelete()}
+          onCancel={() => setShowDeletePropertyPrompt(false)}
+        ></PromptWindow>
+      )}
+    </>
   );
 };

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/FieldTypeName.tsx

@@ -2,7 +2,7 @@ import { FieldType } from '@/services/backend';
 import { useTranslation } from 'react-i18next';
 
 export const FieldTypeName = ({ fieldType }: { fieldType: FieldType }) => {
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
   return (
     <>
       {fieldType === FieldType.RichText && t('grid.field.textFieldName')}

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellNumber.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/InlineEditFields/EditCellNumber.tsx

@@ -11,7 +11,7 @@ export const EditCellNumber = ({
   const [value, setValue] = useState('');
 
   useEffect(() => {
-    setValue(data || '');
+    setValue(data ?? '');
   }, [data]);
 
   const save = async () => {
@@ -23,7 +23,7 @@ export const EditCellNumber = ({
       value={value}
       onChange={(e) => setValue(e.target.value)}
       onBlur={() => save()}
-      className={'w-full px-4 py-2'}
+      className={'w-full px-4 py-1'}
     ></input>
   );
 };

+ 4 - 4
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellText.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/InlineEditFields/EditCellText.tsx

@@ -12,12 +12,12 @@ export const EditCellText = ({
   const [contentRows, setContentRows] = useState(1);
 
   useEffect(() => {
-    setValue(data || '');
+    setValue(data ?? '');
   }, [data]);
 
   useEffect(() => {
     if (!value?.length) return;
-    setContentRows(Math.max(1, (value || '').split('\n').length));
+    setContentRows(Math.max(1, (value ?? '').split('\n').length));
   }, [value]);
 
   const onTextFieldChange = async (v: string) => {
@@ -29,9 +29,9 @@ export const EditCellText = ({
   };
 
   return (
-    <div className={''}>
+    <div>
       <textarea
-        className={'mt-0.5 h-full w-full resize-none px-4 py-2'}
+        className={'mt-0.5 h-full w-full resize-none px-4 py-1'}
         rows={contentRows}
         value={value}
         onChange={(e) => onTextFieldChange(e.target.value)}

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellUrl.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/InlineEditFields/EditCellUrl.tsx

@@ -13,7 +13,7 @@ export const EditCellUrl = ({
   const [value, setValue] = useState('');
 
   useEffect(() => {
-    setValue((data as URLCellDataPB)?.url || '');
+    setValue((data as URLCellDataPB)?.url ?? '');
   }, [data]);
 
   const save = async () => {
@@ -25,7 +25,7 @@ export const EditCellUrl = ({
       value={value}
       onChange={(e) => setValue(e.target.value)}
       onBlur={() => save()}
-      className={'w-full px-4 py-2'}
+      className={'w-full px-4 py-1'}
     ></input>
   );
 };

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCheckboxCell.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/InlineEditFields/EditCheckboxCell.tsx

@@ -18,7 +18,7 @@ export const EditCheckboxCell = ({
   };
 
   return (
-    <div onClick={() => toggleValue()} className={'block px-4 py-2'}>
+    <div onClick={() => toggleValue()} className={'block px-4 py-1'}>
       <button className={'h-5 w-5'}>
         {data === 'Yes' ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
       </button>

+ 69 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOption.tsx

@@ -0,0 +1,69 @@
+import { SelectOptionColorPB, SelectOptionPB } from '@/services/backend';
+import { getBgColor } from '$app/components/_shared/getColor';
+import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
+import { Details2Svg } from '$app/components/_shared/svg/Details2Svg';
+import { ISelectOption } from '$app_reducers/database/slice';
+import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
+import { MouseEventHandler } from 'react';
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+
+export const CellOption = ({
+  option,
+  checked,
+  cellIdentifier,
+  openOptionDetail,
+  clearValue,
+}: {
+  option: ISelectOption;
+  checked: boolean;
+  cellIdentifier: CellIdentifier;
+  openOptionDetail: (_left: number, _top: number, _select_option: SelectOptionPB) => void;
+  clearValue: () => void;
+}) => {
+  const onOptionDetailClick: MouseEventHandler = (e) => {
+    e.stopPropagation();
+    let target = e.target as HTMLElement;
+
+    while (!(target instanceof HTMLButtonElement)) {
+      if (target.parentElement === null) return;
+      target = target.parentElement;
+    }
+
+    const selectOption = new SelectOptionPB({
+      id: option.selectOptionId,
+      name: option.title,
+      color: option.color ?? SelectOptionColorPB.Purple,
+    });
+
+    const { right: _left, top: _top } = target.getBoundingClientRect();
+    openOptionDetail(_left, _top, selectOption);
+  };
+
+  const onToggleOptionClick: MouseEventHandler = async () => {
+    if (checked) {
+      await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.selectOptionId]);
+    } else {
+      await new SelectOptionCellBackendService(cellIdentifier).selectOption([option.selectOptionId]);
+    }
+    clearValue();
+  };
+
+  return (
+    <div
+      onClick={onToggleOptionClick}
+      className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'}
+    >
+      <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`}>{option.title}</div>
+      <div className={'flex items-center'}>
+        {checked && (
+          <button className={'h-5 w-5 p-1'}>
+            <CheckmarkSvg></CheckmarkSvg>
+          </button>
+        )}
+        <button onClick={onOptionDetailClick} className={'h-6 w-6 p-1'}>
+          <Details2Svg></Details2Svg>
+        </button>
+      </div>
+    </div>
+  );
+};

+ 5 - 9
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CellOptions.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOptions.tsx

@@ -1,6 +1,6 @@
 import { SelectOptionCellDataPB } from '@/services/backend';
 import { getBgColor } from '$app/components/_shared/getColor';
-import { useRef } from 'react';
+import { MouseEventHandler, useRef } from 'react';
 
 export const CellOptions = ({
   data,
@@ -11,23 +11,19 @@ export const CellOptions = ({
 }) => {
   const ref = useRef<HTMLDivElement>(null);
 
-  const onClick = () => {
+  const onClick: MouseEventHandler = () => {
     if (!ref.current) return;
     const { left, top } = ref.current.getBoundingClientRect();
     onEditClick(left, top);
   };
 
   return (
-    <div
-      ref={ref}
-      onClick={() => onClick()}
-      className={'flex w-full flex-wrap items-center gap-2 px-4 py-2 text-xs text-black'}
-    >
+    <div ref={ref} onClick={onClick} className={'flex w-full flex-wrap items-center gap-2 px-4 py-1 text-xs text-black'}>
       {data?.select_options?.map((option, index) => (
         <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
-          {option?.name || ''}
+          {option?.name ?? ''}
         </div>
-      )) || ''}
+      ))}
       &nbsp;
     </div>
   );

+ 104 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/CellOptionsPopup.tsx

@@ -0,0 +1,104 @@
+import { KeyboardEventHandler, useEffect, useRef, useState } from 'react';
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+import { useCell } from '$app/components/_shared/database-hooks/useCell';
+import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
+import { FieldController } from '$app/stores/effects/database/field/field_controller';
+import { SelectOptionCellDataPB, SelectOptionPB } from '@/services/backend';
+import { useTranslation } from 'react-i18next';
+import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
+import { useAppSelector } from '$app/stores/store';
+import { ISelectOptionType } from '$app_reducers/database/slice';
+import { PopupWindow } from '$app/components/_shared/PopupWindow';
+import { CellOption } from '$app/components/_shared/EditRow/Options/CellOption';
+import { SelectedOption } from '$app/components/_shared/EditRow/Options/SelectedOption';
+
+export const CellOptionsPopup = ({
+  top,
+  left,
+  cellIdentifier,
+  cellCache,
+  fieldController,
+  onOutsideClick,
+  openOptionDetail,
+}: {
+  top: number;
+  left: number;
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+  onOutsideClick: () => void;
+  openOptionDetail: (_left: number, _top: number, _select_option: SelectOptionPB) => void;
+}) => {
+  const inputRef = useRef<HTMLInputElement>(null);
+  const { t } = useTranslation();
+  const [value, setValue] = useState('');
+  const { data } = useCell(cellIdentifier, cellCache, fieldController);
+  const databaseStore = useAppSelector((state) => state.database);
+
+  useEffect(() => {
+    if (inputRef?.current) {
+      inputRef.current.focus();
+    }
+  }, [inputRef]);
+
+  const onKeyDown: KeyboardEventHandler = async (e) => {
+    if (e.key === 'Enter' && value.length > 0) {
+      await new SelectOptionCellBackendService(cellIdentifier).createOption({ name: value });
+      setValue('');
+    }
+  };
+
+  const onKeyDownWrapper: KeyboardEventHandler = (e) => {
+    if (e.key === 'Escape') {
+      onOutsideClick();
+    }
+  };
+
+  return (
+    <PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
+      <div onKeyDown={onKeyDownWrapper} className={'flex flex-col gap-2 p-2'}>
+        <div className={'border-shades-3 flex flex-1 items-center gap-2 rounded border bg-main-selector px-2 '}>
+          <div className={'flex flex-wrap items-center gap-2 text-black'}>
+            {(data as SelectOptionCellDataPB)?.select_options?.map((option, index) => (
+              <SelectedOption
+                option={option}
+                key={index}
+                cellIdentifier={cellIdentifier}
+                clearValue={() => setValue('')}
+              ></SelectedOption>
+            ))}
+          </div>
+          <input
+            ref={inputRef}
+            className={'py-2'}
+            value={value}
+            onChange={(e) => setValue(e.target.value)}
+            placeholder={t('grid.selectOption.searchOption') ?? ''}
+            onKeyDown={onKeyDown}
+          />
+          <div className={'font-mono text-shade-3'}>{value.length}/30</div>
+        </div>
+        <div className={'-mx-4 h-[1px] bg-shade-6'}></div>
+        <div className={'font-medium text-shade-3'}>{t('grid.selectOption.panelTitle') ?? ''}</div>
+        <div className={'flex flex-col gap-1'}>
+          {(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType).selectOptions.map(
+            (option, index) => (
+              <CellOption
+                key={index}
+                option={option}
+                checked={
+                  !!(data as SelectOptionCellDataPB)?.select_options?.find(
+                    (selectedOption) => selectedOption.id === option.selectOptionId
+                  )
+                }
+                cellIdentifier={cellIdentifier}
+                openOptionDetail={openOptionDetail}
+                clearValue={() => setValue('')}
+              ></CellOption>
+            )
+          )}
+        </div>
+      </div>
+    </PopupWindow>
+  );
+};

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellOptionPopup.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/EditCellOptionPopup.tsx

@@ -22,7 +22,7 @@ export const EditCellOptionPopup = ({
   onOutsideClick: () => void;
 }) => {
   const inputRef = useRef<HTMLInputElement>(null);
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
   const [value, setValue] = useState('');
 
   useEffect(() => {

+ 30 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/Options/SelectedOption.tsx

@@ -0,0 +1,30 @@
+import { getBgColor } from '$app/components/_shared/getColor';
+import { CloseSvg } from '$app/components/_shared/svg/CloseSvg';
+import { SelectOptionPB } from '@/services/backend';
+import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+import { MouseEventHandler } from 'react';
+
+export const SelectedOption = ({
+  option,
+  cellIdentifier,
+  clearValue,
+}: {
+  option: SelectOptionPB;
+  cellIdentifier: CellIdentifier;
+  clearValue: () => void;
+}) => {
+  const onUnselectOptionClick: MouseEventHandler = async () => {
+    await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.id]);
+    clearValue();
+  };
+
+  return (
+    <div className={`${getBgColor(option.color)} flex items-center gap-0.5 rounded px-1 py-0.5`}>
+      <span>{option?.name ?? ''}</span>
+      <button onClick={onUnselectOptionClick} className={'h-5 w-5 cursor-pointer'}>
+        <CloseSvg></CloseSvg>
+      </button>
+    </div>
+  );
+};

+ 169 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/PropertiesPanel.tsx

@@ -0,0 +1,169 @@
+import { DropDownShowSvg } from '$app/components/_shared/svg/DropDownShowSvg';
+import { useEffect, useState } from 'react';
+import { useRow } from '$app/components/_shared/database-hooks/useRow';
+import { DatabaseController } from '$app/stores/effects/database/database_controller';
+import { RowInfo } from '$app/stores/effects/database/row/row_cache';
+import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
+import { useAppSelector } from '$app/stores/store';
+import { Switch } from '$app/components/_shared/Switch';
+import { FieldType } from '@/services/backend';
+import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
+import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
+import { MultiSelectTypeSvg } from '$app/components/_shared/svg/MultiSelectTypeSvg';
+import { DocumentSvg } from '$app/components/_shared/svg/DocumentSvg';
+import { SingleSelectTypeSvg } from '$app/components/_shared/svg/SingleSelectTypeSvg';
+
+const typesOrder: FieldType[] = [
+  FieldType.RichText,
+  FieldType.Number,
+  FieldType.DateTime,
+  FieldType.SingleSelect,
+  FieldType.MultiSelect,
+  FieldType.Checkbox,
+  FieldType.URL,
+  FieldType.Checklist,
+];
+
+export const PropertiesPanel = ({
+  viewId,
+  controller,
+  rowInfo,
+  onDeletePropertyClick,
+}: {
+  viewId: string;
+  controller: DatabaseController;
+  rowInfo: RowInfo;
+  onDeletePropertyClick: (fieldId: string) => void;
+}) => {
+  const { cells } = useRow(viewId, controller, rowInfo);
+  const databaseStore = useAppSelector((state) => state.database);
+
+  const [showAddedProperties, setShowAddedProperties] = useState(true);
+  const [showBasicProperties, setShowBasicProperties] = useState(false);
+  const [showAdvancedProperties, setShowAdvancedProperties] = useState(false);
+
+  const [hoveredPropertyIndex, setHoveredPropertyIndex] = useState(-1);
+  const [hiddenProperties, setHiddenProperties] = useState<boolean[]>([]);
+
+  useEffect(() => {
+    setHiddenProperties(cells.map(() => false));
+  }, [cells]);
+
+  const toggleHideProperty = (v: boolean, index: number) => {
+    setHiddenProperties(hiddenProperties.map((h, i) => (i === index ? !v : h)));
+  };
+
+  return (
+    <div className={'flex flex-col gap-2 overflow-auto py-12 px-4'}>
+      <div
+        onClick={() => setShowAddedProperties(!showAddedProperties)}
+        className={'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-shade-6'}
+      >
+        <div className={'text-sm'}>Added Properties</div>
+        <i className={`h-5 w-5 transition-transform duration-500 ${showAddedProperties && 'rotate-180'}`}>
+          <DropDownShowSvg></DropDownShowSvg>
+        </i>
+      </div>
+      <div className={'flex flex-col text-xs'} onMouseLeave={() => setHoveredPropertyIndex(-1)}>
+        {showAddedProperties &&
+          cells.map((cell, cellIndex) => (
+            <div
+              key={cellIndex}
+              onMouseEnter={() => setHoveredPropertyIndex(cellIndex)}
+              className={
+                'flex cursor-pointer items-center justify-between gap-4 rounded-lg px-2 py-1 hover:bg-main-secondary'
+              }
+            >
+              <div className={'flex items-center gap-2 text-black'}>
+                <div className={'flex h-5 w-5 flex-shrink-0 items-center justify-center'}>
+                  <FieldTypeIcon fieldType={cell.cellIdentifier.fieldType}></FieldTypeIcon>
+                </div>
+                <span className={'overflow-hidden text-ellipsis whitespace-nowrap'}>
+                  {databaseStore.fields[cell.cellIdentifier.fieldId]?.title ?? ''}
+                </span>
+              </div>
+              <div className={'flex items-center'}>
+                <i
+                  onClick={() => onDeletePropertyClick(cell.cellIdentifier.fieldId)}
+                  className={`h-[16px] w-[16px] text-black transition-opacity duration-300 ${
+                    hoveredPropertyIndex === cellIndex ? 'opacity-100' : 'opacity-0'
+                  }`}
+                >
+                  <TrashSvg></TrashSvg>
+                </i>
+                <Switch value={!hiddenProperties[cellIndex]} setValue={(v) => toggleHideProperty(v, cellIndex)}></Switch>
+              </div>
+            </div>
+          ))}
+      </div>
+      <div
+        onClick={() => setShowBasicProperties(!showBasicProperties)}
+        className={'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-shade-6'}
+      >
+        <div className={'text-sm'}>Basic Properties</div>
+        <i className={`h-5 w-5 transition-transform duration-500 ${showBasicProperties && 'rotate-180'}`}>
+          <DropDownShowSvg></DropDownShowSvg>
+        </i>
+      </div>
+      <div className={'flex flex-col gap-2 text-xs'}>
+        {showBasicProperties && (
+          <div className={'flex flex-col'}>
+            {typesOrder.map((t, i) => (
+              <button
+                onClick={() => console.log('type clicked')}
+                key={i}
+                className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
+              >
+                <i className={'h-5 w-5'}>
+                  <FieldTypeIcon fieldType={t}></FieldTypeIcon>
+                </i>
+                <span>
+                  <FieldTypeName fieldType={t}></FieldTypeName>
+                </span>
+              </button>
+            ))}
+          </div>
+        )}
+      </div>
+      <div
+        onClick={() => setShowAdvancedProperties(!showAdvancedProperties)}
+        className={'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-shade-6'}
+      >
+        <div className={'text-sm'}>Advanced Properties</div>
+        <i className={`h-5 w-5 transition-transform duration-500 ${showAdvancedProperties && 'rotate-180'}`}>
+          <DropDownShowSvg></DropDownShowSvg>
+        </i>
+      </div>
+      <div className={'flex flex-col gap-2 text-xs'}>
+        {showAdvancedProperties && (
+          <div className={'flex flex-col'}>
+            <button
+              className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
+            >
+              <i className={'h-5 w-5'}>
+                <MultiSelectTypeSvg></MultiSelectTypeSvg>
+              </i>
+              <span>Last edited time</span>
+            </button>
+            <button
+              className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
+            >
+              <i className={'h-5 w-5'}>
+                <DocumentSvg></DocumentSvg>
+              </i>
+              <span>Document</span>
+            </button>
+            <button
+              className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
+            >
+              <i className={'h-5 w-5'}>
+                <SingleSelectTypeSvg></SingleSelectTypeSvg>
+              </i>
+              <span>Relation to</span>
+            </button>
+          </div>
+        )}
+      </div>
+    </div>
+  );
+};

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/_shared/PopupWindow.tsx

@@ -9,7 +9,7 @@ export const PopupWindow = ({
   top,
 }: {
   children: ReactNode;
-  className: string;
+  className?: string;
   onOutsideClick: () => void;
   left: number;
   top: number;
@@ -33,7 +33,7 @@ export const PopupWindow = ({
     } else {
       setAdjustedLeft(left);
     }
-  }, [ref, left, top, window]);
+  }, [ref, left, top]);
 
   return (
     <div
@@ -41,7 +41,7 @@ export const PopupWindow = ({
       className={
         'fixed z-10 rounded-lg bg-white shadow-md transition-opacity duration-300 ' +
         (adjustedTop === -100 && adjustedLeft === -100 ? 'opacity-0 ' : 'opacity-100 ') +
-        (className || '')
+        (className ?? '')
       }
       style={{ top: `${adjustedTop}px`, left: `${adjustedLeft}px` }}
     >

+ 24 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/PromptWindow.tsx

@@ -0,0 +1,24 @@
+import { Button } from '$app/components/_shared/Button';
+
+export const PromptWindow = ({ msg, onYes, onCancel }: { msg: string; onYes: () => void; onCancel: () => void }) => {
+  return (
+    <div
+      className='fixed inset-0 z-20 flex items-center justify-center bg-black/30 backdrop-blur-sm'
+      onClick={() => onCancel()}
+    >
+      <div className={'rounded-xl bg-white p-16'} onClick={(e) => e.stopPropagation()}>
+        <div className={'flex flex-col items-center justify-center gap-8'}>
+          <div className={'text-black'}>{msg}</div>
+          <div className={'flex items-center justify-around gap-4'}>
+            <Button onClick={() => onCancel()} size={'medium-transparent'}>
+              Cancel
+            </Button>
+            <Button onClick={() => onYes()} size={'medium'}>
+              Yes
+            </Button>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};

+ 8 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/Switch.tsx

@@ -0,0 +1,8 @@
+export const Switch = ({ value, setValue }: { value: boolean; setValue: (v: boolean) => void }) => {
+  return (
+    <label className='form-switch' style={{ transform: 'scale(0.5)', marginRight: '-16px' }}>
+      <input type='checkbox' checked={value} onChange={() => setValue(!value)} />
+      <i></i>
+    </label>
+  );
+};

+ 12 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/DragElementSvg.tsx

@@ -0,0 +1,12 @@
+export const DragElementSvg = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <rect x='9' y='3' width='2' height='2' rx='0.5' fill='currentColor' />
+      <rect x='5' y='3' width='2' height='2' rx='0.5' fill='currentColor' />
+      <rect x='9' y='7' width='2' height='2' rx='0.5' fill='currentColor' />
+      <rect x='5' y='7' width='2' height='2' rx='0.5' fill='currentColor' />
+      <rect x='9' y='11' width='2' height='2' rx='0.5' fill='currentColor' />
+      <rect x='5' y='11' width='2' height='2' rx='0.5' fill='currentColor' />
+    </svg>
+  );
+};

+ 9 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/ImageSvg.tsx

@@ -0,0 +1,9 @@
+export const ImageSvg = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <rect x='1.5' y='3' width='13' height='10' rx='1.5' stroke='currentColor' />
+      <circle cx='5.5' cy='6.5' r='1' stroke='currentColor' />
+      <path d='M5 13L10.112 8.45603C10.4211 8.18126 10.8674 8.12513 11.235 8.31482L14.5 10' stroke='currentColor' />
+    </svg>
+  );
+};

+ 4 - 5
frontend/appflowy_tauri/src/appflowy_app/components/auth/Login/Login.tsx

@@ -9,11 +9,10 @@ import { EarthSvg } from '../../_shared/svg/EarthSvg';
 import { useState } from 'react';
 import { LanguageSelectPopup } from '../../_shared/LanguageSelectPopup';
 
-
 export const Login = () => {
   const { showPassword, onTogglePassword, onSignInClick, email, setEmail, password, setPassword, authError } =
     useLogin();
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
   const [showLanguagePopup, setShowLanguagePopup] = useState(false);
 
   return (
@@ -34,17 +33,17 @@ export const Login = () => {
             <input
               type='text'
               className={`input w-full ${authError && 'error'}`}
-              placeholder={t('signIn.emailHint') || ''}
+              placeholder={t('signIn.emailHint') ?? ''}
               value={email}
               onChange={(e) => setEmail(e.target.value)}
             />
             <div className='relative w-full'>
               {/* Password input field */}
-              
+
               <input
                 type={showPassword ? 'text' : 'password'}
                 className={`input w-full  !pr-10 ${authError && 'error'}`}
-                placeholder={t('signIn.passwordHint') || ''}
+                placeholder={t('signIn.passwordHint') ?? ''}
                 value={password}
                 onChange={(e) => setPassword(e.target.value)}
               />

+ 4 - 4
frontend/appflowy_tauri/src/appflowy_app/components/auth/SignUp/SignUp.tsx

@@ -27,7 +27,7 @@ export const SignUp = () => {
     setRepeatedPassword,
     authError,
   } = useSignUp();
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
   const [showLanguagePopup, setShowLanguagePopup] = useState(false);
 
   return (
@@ -45,7 +45,7 @@ export const SignUp = () => {
           <input
             type='text'
             className={`input w-full ${authError && 'error'}`}
-            placeholder={t('signUp.emailHint') || ''}
+            placeholder={t('signUp.emailHint') ?? ''}
             value={email}
             onChange={(e) => setEmail(e.target.value)}
           />
@@ -61,7 +61,7 @@ export const SignUp = () => {
             <input
               type={showPassword ? 'text' : 'password'}
               className={`input w-full !pr-10 ${authError && 'error'}`}
-              placeholder={t('signUp.passwordHint') || ''}
+              placeholder={t('signUp.passwordHint') ?? ''}
               value={password}
               onChange={(e) => setPassword(e.target.value)}
             />
@@ -79,7 +79,7 @@ export const SignUp = () => {
             <input
               type={showConfirmPassword ? 'text' : 'password'}
               className={`input w-full !pr-10 ${authError && 'error'}`}
-              placeholder={t('signUp.repeatPasswordHint') || ''}
+              placeholder={t('signUp.repeatPasswordHint') ?? ''}
               value={repeatedPassword}
               onChange={(e) => setRepeatedPassword(e.target.value)}
             />

+ 0 - 2
frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx

@@ -1,6 +1,5 @@
 import { SearchInput } from '../_shared/SearchInput';
 import { BoardGroup } from './BoardGroup';
-import { NewBoardBlock } from './NewBoardBlock';
 import { useDatabase } from '../_shared/database-hooks/useDatabase';
 import { ViewLayoutPB } from '@/services/backend';
 import { DragDropContext } from 'react-beautiful-dnd';
@@ -45,7 +44,6 @@ export const Board = ({ viewId, title }: { viewId: string; title: string }) => {
                   onOpenRow={onOpenRow}
                 />
               ))}
-            <NewBoardBlock onClick={() => console.log('new block')}></NewBoardBlock>
           </div>
         </div>
       </DragDropContext>

+ 4 - 1
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx

@@ -8,6 +8,7 @@ import { MouseEventHandler, useState } from 'react';
 import { PopupWindow } from '$app/components/_shared/PopupWindow';
 import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
 import { RowBackendService } from '$app/stores/effects/database/row/row_bd_svc';
+import { useTranslation } from 'react-i18next';
 
 export const BoardCard = ({
   index,
@@ -24,6 +25,8 @@ export const BoardCard = ({
   groupByFieldId: string;
   onOpenRow: (rowId: RowInfo) => void;
 }) => {
+  const { t } = useTranslation();
+
   const { cells } = useRow(viewId, controller, rowInfo);
 
   const [showCardPopup, setShowCardPopup] = useState(false);
@@ -95,7 +98,7 @@ export const BoardCard = ({
             <i className={'h-5 w-5'}>
               <TrashSvg></TrashSvg>
             </i>
-            <span className={'flex-shrink-0'}>Delete</span>
+            <span className={'flex-shrink-0'}>{t('grid.row.delete')}</span>
           </button>
         </PopupWindow>
       )}

+ 8 - 3
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCell.tsx

@@ -7,6 +7,7 @@ import { BoardDateCell } from './BoardDateCell';
 import { BoardTextCell } from './BoardTextCell';
 import { BoardUrlCell } from '$app/components/board/BoardUrlCell';
 import { BoardCheckboxCell } from '$app/components/board/BoardCheckboxCell';
+import { BoardCheckListCell } from '$app/components/board/BoardCheckListCell';
 
 export const BoardCell = ({
   cellIdentifier,
@@ -19,14 +20,18 @@ export const BoardCell = ({
 }) => {
   return (
     <>
-      {cellIdentifier.fieldType === FieldType.SingleSelect ||
-      cellIdentifier.fieldType === FieldType.MultiSelect ||
-      cellIdentifier.fieldType === FieldType.Checklist ? (
+      {cellIdentifier.fieldType === FieldType.SingleSelect || cellIdentifier.fieldType === FieldType.MultiSelect ? (
         <BoardOptionsCell
           cellIdentifier={cellIdentifier}
           cellCache={cellCache}
           fieldController={fieldController}
         ></BoardOptionsCell>
+      ) : cellIdentifier.fieldType === FieldType.Checklist ? (
+        <BoardCheckListCell
+          cellIdentifier={cellIdentifier}
+          cellCache={cellCache}
+          fieldController={fieldController}
+        ></BoardCheckListCell>
       ) : cellIdentifier.fieldType === FieldType.DateTime ? (
         <BoardDateCell
           cellIdentifier={cellIdentifier}

+ 37 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCheckListCell.tsx

@@ -0,0 +1,37 @@
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
+import { FieldController } from '$app/stores/effects/database/field/field_controller';
+import { useCell } from '$app/components/_shared/database-hooks/useCell';
+import { useEffect, useState } from 'react';
+import { ISelectOptionType } from '$app_reducers/database/slice';
+import { SelectOptionCellDataPB } from '@/services/backend';
+import { useAppSelector } from '$app/stores/store';
+import { CheckListProgress } from '$app/components/_shared/CheckListProgress';
+
+export const BoardCheckListCell = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const { data } = useCell(cellIdentifier, cellCache, fieldController);
+
+  const databaseStore = useAppSelector((state) => state.database);
+  const [allOptionsCount, setAllOptionsCount] = useState(0);
+  const [selectedOptionsCount, setSelectedOptionsCount] = useState(0);
+
+  useEffect(() => {
+    setAllOptionsCount(
+      (databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType)?.selectOptions?.length ?? 0
+    );
+  }, [databaseStore, cellIdentifier]);
+
+  useEffect(() => {
+    setSelectedOptionsCount((data as SelectOptionCellDataPB)?.select_options?.length ?? 0);
+  }, [data]);
+
+  return <CheckListProgress completed={selectedOptionsCount} max={allOptionsCount} />;
+};

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardDateCell.tsx

@@ -14,5 +14,5 @@ export const BoardDateCell = ({
   fieldController: FieldController;
 }) => {
   const { data } = useCell(cellIdentifier, cellCache, fieldController);
-  return <div>{(data as DateCellDataPB | undefined)?.date || ''}</div>;
+  return <div>{(data as DateCellDataPB | undefined)?.date ?? ''}&nbsp;</div>;
 };

+ 4 - 1
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroup.tsx

@@ -5,6 +5,7 @@ import { RowInfo } from '$app/stores/effects/database/row/row_cache';
 import { DatabaseController } from '$app/stores/effects/database/database_controller';
 import { Droppable } from 'react-beautiful-dnd';
 import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
+import { useTranslation } from 'react-i18next';
 
 export const BoardGroup = ({
   viewId,
@@ -23,6 +24,8 @@ export const BoardGroup = ({
   onOpenRow: (rowId: RowInfo) => void;
   group: DatabaseGroupController;
 }) => {
+  const { t } = useTranslation();
+
   return (
     <div className={'flex h-full w-[250px] flex-col rounded-lg bg-surface-1'}>
       <div className={'flex items-center justify-between p-4'}>
@@ -73,7 +76,7 @@ export const BoardGroup = ({
           <span className={'h-5 w-5'}>
             <AddSvg></AddSvg>
           </span>
-          <span>New</span>
+          <span>{t('board.column.create_new_card')}</span>
         </button>
       </div>
     </div>

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardOptionsCell.tsx

@@ -20,9 +20,9 @@ export const BoardOptionsCell = ({
     <div className={'flex flex-wrap items-center gap-2 py-2 text-xs text-black'}>
       {(data as SelectOptionCellDataPB)?.select_options?.map((option, index) => (
         <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
-          {option?.name || ''}
+          {option?.name ?? ''}
         </div>
-      )) || ''}
+      ))}
       &nbsp;
     </div>
   );

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardSettingsPopup.tsx

@@ -14,7 +14,7 @@ export const BoardSettingsPopup = ({
   onGroupClick: () => void;
 }) => {
   const [settingsItems, setSettingsItems] = useState<IPopupItem[]>([]);
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
   useEffect(() => {
     setSettingsItems([
       {

+ 2 - 1
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardTextCell.tsx

@@ -16,9 +16,10 @@ export const BoardTextCell = ({
 
   return (
     <div>
-      {((data as string | undefined) || '').split('\n').map((line, index) => (
+      {((data as string | undefined) ?? '').split('\n').map((line, index) => (
         <div key={index}>{line}</div>
       ))}
+      &nbsp;
     </div>
   );
 };

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardUrlCell.tsx

@@ -17,8 +17,8 @@ export const BoardUrlCell = ({
 
   return (
     <>
-      <a className={'text-main-accent hover:underline'} href={(data as URLCellDataPB)?.url || ''} target={'_blank'}>
-        {(data as URLCellDataPB)?.content || ''}
+      <a className={'text-main-accent hover:underline'} href={(data as URLCellDataPB)?.url ?? ''} target={'_blank'}>
+        {(data as URLCellDataPB)?.content ?? ''}&nbsp;
       </a>
     </>
   );

+ 0 - 14
frontend/appflowy_tauri/src/appflowy_app/components/board/NewBoardBlock.tsx

@@ -1,14 +0,0 @@
-import AddSvg from '../_shared/svg/AddSvg';
-
-export const NewBoardBlock = ({ onClick }: { onClick: () => void }) => {
-  return (
-    <div className={'w-[250px]'}>
-      <button onClick={onClick} className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-surface-2'}>
-        <span className={'h-5 w-5'}>
-          <AddSvg></AddSvg>
-        </span>
-        <span>Add Block</span>
-      </button>
-    </div>
-  );
-};

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridCheckBox.tsx

@@ -1,7 +1,7 @@
 import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell_bd_svc';
 import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
 import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
-import { EditCheckboxCell } from '../../_shared/EditRow/EditCheckboxCell';
+import { EditCheckboxCell } from '../../_shared/EditRow/InlineEditFields/EditCheckboxCell';
 import { useCell } from '../../_shared/database-hooks/useCell';
 
 export const GridCheckBox = ({

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridDate.tsx

@@ -3,9 +3,9 @@ import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cach
 import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
 import { useCell } from '../../_shared/database-hooks/useCell';
 import { DateCellDataPB } from '@/services/backend';
-import { EditCellDate } from '../../_shared/EditRow/EditCellDate';
+import { EditCellDate } from '../../_shared/EditRow/Date/EditCellDate';
 import { useState } from 'react';
-import { DatePickerPopup } from '../../_shared/EditRow/DatePickerPopup';
+import { DatePickerPopup } from '../../_shared/EditRow/Date/DatePickerPopup';
 
 export const GridDate = ({
   cellIdentifier,

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridNumberCell.tsx

@@ -2,7 +2,7 @@ import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell
 import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
 import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
 import { useCell } from '../../_shared/database-hooks/useCell';
-import { EditCellNumber } from '../../_shared/EditRow/EditCellNumber';
+import { EditCellNumber } from '../../_shared/EditRow/InlineEditFields/EditCellNumber';
 
 export const GridNumberCell = ({
   cellIdentifier,

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridSingleSelectOptions.tsx

@@ -1,11 +1,11 @@
 import { useState } from 'react';
-import { CellOptions } from '$app/components/_shared/EditRow/CellOptions';
+import { CellOptions } from '$app/components/_shared/EditRow/Options/CellOptions';
 import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell_bd_svc';
 import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
 import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
 import { useCell } from '$app/components/_shared/database-hooks/useCell';
-import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPopup';
-import { EditCellOptionPopup } from '$app/components/_shared/EditRow/EditCellOptionPopup';
+import { CellOptionsPopup } from '$app/components/_shared/EditRow/Options/CellOptionsPopup';
+import { EditCellOptionPopup } from '$app/components/_shared/EditRow/Options/EditCellOptionPopup';
 import { SelectOptionCellDataPB, SelectOptionPB } from '@/services/backend';
 
 export default function GridSingleSelectOptions({

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridTextCell.tsx

@@ -2,7 +2,7 @@ import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell
 import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
 import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
 import { useCell } from '../../_shared/database-hooks/useCell';
-import { EditCellText } from '../../_shared/EditRow/EditCellText';
+import { EditCellText } from '../../_shared/EditRow/InlineEditFields/EditCellText';
 
 export default function GridTextCell({
   cellIdentifier,

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridUrl.tsx

@@ -2,7 +2,7 @@ import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell
 import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
 import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
 import { useCell } from '../../_shared/database-hooks/useCell';
-import { EditCellUrl } from '../../_shared/EditRow/EditCellUrl';
+import { EditCellUrl } from '../../_shared/EditRow/InlineEditFields/EditCellUrl';
 import { URLCellDataPB } from '@/services/backend';
 
 export const GridUrl = ({

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeader.tsx

@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
 
 export const GridTableHeader = ({ controller }: { controller: DatabaseController }) => {
   const { fields, onAddField } = useGridTableHeaderHooks(controller);
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
 
   return (
     <>

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridAddRow.tsx

@@ -4,7 +4,7 @@ import { useGridAddRow } from './GridAddRow.hooks';
 import { useTranslation } from 'react-i18next';
 export const GridAddRow = ({ controller }: { controller: DatabaseController }) => {
   const { addRow } = useGridAddRow(controller);
-  const { t } = useTranslation('');
+  const { t } = useTranslation();
 
   return (
     <div>

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx

@@ -21,8 +21,8 @@ export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boole
   useEffect(() => {
     const page = pagesStore.find((p) => p.id === activePageId);
     const folder = foldersStore.find((f) => f.id === page?.folderId);
-    setFolderName(folder?.title || '');
-    setPageName(page?.title || '');
+    setFolderName(folder?.title ?? '');
+    setPageName(page?.title ?? '');
   }, [pagesStore, foldersStore, activePageId]);
 
   return (

+ 7 - 7
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts

@@ -1,12 +1,12 @@
-import { foldersActions, IFolder } from '../../../stores/reducers/folders/slice';
+import { foldersActions, IFolder } from '$app_reducers/folders/slice';
 import { useEffect, useState } from 'react';
-import { useAppDispatch, useAppSelector } from '../../../stores/store';
-import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
+import { useAppDispatch, useAppSelector } from '$app/stores/store';
+import { IPage, pagesActions } from '$app_reducers/pages/slice';
 import { ViewLayoutPB } from '@/services/backend';
-import { AppBackendService } from '../../../stores/effects/folder/app/app_bd_svc';
-import { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc';
+import { AppBackendService } from '$app/stores/effects/folder/app/app_bd_svc';
+import { WorkspaceBackendService } from '$app/stores/effects/folder/workspace/workspace_bd_svc';
 
-import { AppObserver } from '../../../stores/effects/folder/app/app_observer';
+import { AppObserver } from '$app/stores/effects/folder/app/app_observer';
 import { useNavigate } from 'react-router-dom';
 import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants';
 
@@ -32,7 +32,7 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
 
   // Backend services
   const appBackendService = new AppBackendService(folder.id);
-  const workspaceBackendService = new WorkspaceBackendService(workspace.id || '');
+  const workspaceBackendService = new WorkspaceBackendService(workspace.id ?? '');
 
   useEffect(() => {
     void appObserver.subscribe({

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.tsx

@@ -2,9 +2,9 @@ import { Details2Svg } from '../../_shared/svg/Details2Svg';
 import AddSvg from '../../_shared/svg/AddSvg';
 import { NavItemOptionsPopup } from './NavItemOptionsPopup';
 import { NewPagePopup } from './NewPagePopup';
-import { IFolder } from '../../../stores/reducers/folders/slice';
+import { IFolder } from '$app_reducers/folders/slice';
 import { useFolderEvents } from './FolderItem.hooks';
-import { IPage } from '../../../stores/reducers/pages/slice';
+import { IPage } from '$app_reducers/pages/slice';
 import { PageItem } from './PageItem';
 import { Button } from '../../_shared/Button';
 import { RenamePopup } from './RenamePopup';

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.hooks.ts

@@ -5,7 +5,7 @@ import { WorkspaceBackendService } from '../../../stores/effects/folder/workspac
 export const useNewFolder = () => {
   const appDispatch = useAppDispatch();
   const workspace = useAppSelector((state) => state.workspace);
-  const workspaceBackendService = new WorkspaceBackendService(workspace.id || '');
+  const workspaceBackendService = new WorkspaceBackendService(workspace.id ?? '');
 
   const onNewFolder = async () => {
     const newApp = await workspaceBackendService.createApp({

+ 6 - 5
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts

@@ -1,10 +1,10 @@
-import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
-import { useAppDispatch } from '../../../stores/store';
+import { IPage, pagesActions } from '$app_reducers/pages/slice';
+import { useAppDispatch } from '$app/stores/store';
 import { useEffect, useState } from 'react';
 import { nanoid } from 'nanoid';
-import { ViewBackendService } from '../../../stores/effects/folder/view/view_bd_svc';
-import { useError } from '../../error/Error.hooks';
+import { ViewBackendService } from '$app/stores/effects/folder/view/view_bd_svc';
 import { useLocation } from 'react-router-dom';
+import { ViewPB } from '@/services/backend';
 
 export const usePageEvents = (page: IPage) => {
   const appDispatch = useAppDispatch();
@@ -41,8 +41,9 @@ export const usePageEvents = (page: IPage) => {
     appDispatch(pagesActions.deletePage({ id: page.id }));
   };
 
-  const duplicatePage = () => {
+  const duplicatePage = async () => {
     closePopup();
+    await viewBackendService.duplicate(ViewPB.fromObject({}));
     appDispatch(
       pagesActions.addPage({ id: nanoid(8), pageType: page.pageType, title: page.title, folderId: page.folderId })
     );

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/layout/Workspace.hooks.ts

@@ -9,7 +9,7 @@ export const useWorkspace = () => {
 
   const appDispatch = useAppDispatch();
 
-  const userBackendService: UserBackendService = new UserBackendService(currentUser.id || 0);
+  const userBackendService: UserBackendService = new UserBackendService(currentUser.id ?? 0);
 
   const loadWorkspaceItems = async () => {
     try {

+ 8 - 0
frontend/appflowy_tauri/src/appflowy_app/components/tests/AllIcons.tsx

@@ -37,6 +37,8 @@ import { SortSvg } from '$app/components/_shared/svg/SortSvg';
 import { TextTypeSvg } from '$app/components/_shared/svg/TextTypeSvg';
 import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
 import { UrlTypeSvg } from '$app/components/_shared/svg/UrlTypeSvg';
+import { DragElementSvg } from '$app/components/_shared/svg/DragElementSvg';
+import { ImageSvg } from '$app/components/_shared/svg/ImageSvg';
 
 export const AllIcons = () => {
   return (
@@ -83,6 +85,9 @@ export const AllIcons = () => {
           <i className={'h-5 w-5'} title={'DocumentSvg'}>
             <DocumentSvg></DocumentSvg>
           </i>
+          <i className={'h-5 w-5'} title={'DragElementSvg'}>
+            <DragElementSvg></DragElementSvg>
+          </i>
           <i className={'h-5 w-5'} title={'DropDownShowSvg'}>
             <DropDownShowSvg></DropDownShowSvg>
           </i>
@@ -116,6 +121,9 @@ export const AllIcons = () => {
           <i className={'h-5 w-5'} title={'HideMenuSvg'}>
             <HideMenuSvg></HideMenuSvg>
           </i>
+          <i className={'h-5 w-5'} title={'ImageSvg'}>
+            <ImageSvg></ImageSvg>
+          </i>
           <i className={'h-5 w-5'} title={'InformationSvg'}>
             <InformationSvg></InformationSvg>
           </i>

+ 1 - 0
frontend/appflowy_tauri/src/main.tsx

@@ -5,5 +5,6 @@ import './styles/tailwind.css';
 import './styles/font.css';
 import './styles/template.css';
 import './styles/Calendar.css';
+import './styles/switch.css';
 
 ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);

+ 58 - 0
frontend/appflowy_tauri/src/styles/switch.css

@@ -0,0 +1,58 @@
+.form-switch {
+  display: inline-block;
+  cursor: pointer;
+  -webkit-tap-highlight-color: transparent;
+}
+.form-switch i {
+  position: relative;
+  display: inline-block;
+  margin-right: 0.5rem;
+  width: 46px;
+  height: 26px;
+  @apply bg-shade-6;
+  border-radius: 23px;
+  vertical-align: text-bottom;
+  transition: all 0.3s linear;
+}
+.form-switch i::before {
+  content: '';
+  position: absolute;
+  left: 0;
+  width: 42px;
+  height: 22px;
+  background-color: #fff;
+  border-radius: 11px;
+  transform: translate3d(2px, 2px, 0) scale3d(1, 1, 1);
+  transition: all 0.25s linear;
+}
+.form-switch i::after {
+  content: '';
+  position: absolute;
+  left: 0;
+  width: 22px;
+  height: 22px;
+  background-color: #fff;
+  border-radius: 11px;
+  box-shadow: 0 2px 2px rgba(0, 0, 0, 0.24);
+  transform: translate3d(2px, 2px, 0);
+  transition: all 0.2s ease-in-out;
+}
+.form-switch:active i::after {
+  width: 28px;
+  transform: translate3d(2px, 2px, 0);
+}
+.form-switch:active input:checked + i::after {
+  transform: translate3d(16px, 2px, 0);
+}
+.form-switch input {
+  display: none;
+}
+.form-switch input:checked + i {
+  @apply bg-main-accent;
+}
+.form-switch input:checked + i::before {
+  transform: translate3d(18px, 2px, 0) scale3d(0, 0, 0);
+}
+.form-switch input:checked + i::after {
+  transform: translate3d(22px, 2px, 0);
+}