Browse Source

Merge pull request #2230 from Ascarbek/feat/tauri-grid

Feat: tauri grid
qinluhe 2 years ago
parent
commit
c7eb490db4
79 changed files with 1965 additions and 827 deletions
  1. 266 204
      frontend/appflowy_tauri/pnpm-lock.yaml
  2. 19 15
      frontend/appflowy_tauri/src/appflowy_app/App.tsx
  3. 0 44
      frontend/appflowy_tauri/src/appflowy_app/components/TestColors/TestColors.tsx
  4. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CellOptions.tsx
  5. 42 38
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CellOptionsPopup.tsx
  6. 5 28
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/ChangeFieldTypePopup.tsx
  7. 13 31
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/DatePickerPopup.tsx
  8. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellDate.tsx
  9. 195 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellOptionPopup.tsx
  10. 15 9
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellWrapper.tsx
  11. 9 5
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCheckboxCell.tsx
  12. 12 28
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx
  13. 46 16
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx
  14. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/LanguageSelectPopup.tsx
  15. 10 8
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/PopupSelect.tsx
  16. 51 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/PopupWindow.tsx
  17. 9 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts
  18. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EyeClosedSvg.tsx
  19. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EyeOpenSvg.tsx
  20. 10 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/FullView.tsx
  21. 26 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/GroupByFieldSvg.tsx
  22. 11 0
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/GroupBySvg.tsx
  23. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/auth/Login/Login.tsx
  24. 4 4
      frontend/appflowy_tauri/src/appflowy_app/components/auth/SignUp/SignUp.tsx
  25. 5 10
      frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx
  26. 9 3
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx
  27. 11 4
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCell.tsx
  28. 23 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCheckboxCell.tsx
  29. 4 4
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardDateCell.tsx
  30. 35 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardFieldsPopup.tsx
  31. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroup.tsx
  32. 35 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroupFieldsPopup.tsx
  33. 5 5
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardOptionsCell.tsx
  34. 48 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardSettingsPopup.tsx
  35. 37 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardToolbar.hooks.ts
  36. 28 0
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardToolbar.tsx
  37. 2 6
      frontend/appflowy_tauri/src/appflowy_app/components/board/BoardUrlCell.tsx
  38. 7 1
      frontend/appflowy_tauri/src/appflowy_app/components/error/Error.hooks.ts
  39. 2 2
      frontend/appflowy_tauri/src/appflowy_app/components/error/ErrorHandlerPage.tsx
  40. 57 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/Grid/Grid.tsx
  41. 0 1
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridAddView/GridAddView.tsx
  42. 44 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridCell.tsx
  43. 23 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridCheckBox.tsx
  44. 47 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridDate.tsx
  45. 25 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridNumberCell.tsx
  46. 75 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridSingleSelectOptions.tsx
  47. 23 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridTextCell.tsx
  48. 22 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridUrl.tsx
  49. 1 1
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableCount/GridTableCount.hooks.ts
  50. 17 19
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeader.hooks.tsx
  51. 11 28
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeader.tsx
  52. 123 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeaderItem.tsx
  53. 4 7
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridAddRow.hooks.ts
  54. 6 3
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridAddRow.tsx
  55. 16 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableCell.tsx
  56. 0 28
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableItem.hooks.ts
  57. 0 26
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableItem.tsx
  58. 46 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableRow.tsx
  59. 0 9
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableRows.hooks.ts
  60. 16 19
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableRows.tsx
  61. 4 9
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTitle/GridTitle.hooks.ts
  62. 11 5
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTitle/GridTitle.tsx
  63. 55 0
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTitle/GridTitleOptionsPopup.tsx
  64. 0 6
      frontend/appflowy_tauri/src/appflowy_app/components/grid/GridToolbar/GridToolbar.tsx
  65. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/OptionsPopup.tsx
  66. 60 87
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts
  67. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx
  68. 15 2
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx
  69. 4 10
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewFolderButton.hooks.ts
  70. 3 3
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPagePopup.tsx
  71. 7 20
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/PageItem.hooks.ts
  72. 6 11
      frontend/appflowy_tauri/src/appflowy_app/components/layout/Workspace.hooks.ts
  73. 168 0
      frontend/appflowy_tauri/src/appflowy_app/components/tests/AllIcons.tsx
  74. 45 0
      frontend/appflowy_tauri/src/appflowy_app/components/tests/ColorPalette.tsx
  75. 0 0
      frontend/appflowy_tauri/src/appflowy_app/components/tests/TestFonts.tsx
  76. 1 1
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_controller.ts
  77. 6 4
      frontend/appflowy_tauri/src/appflowy_app/views/BoardPage.tsx
  78. 0 9
      frontend/appflowy_tauri/src/appflowy_app/views/GridPage.hooks.ts
  79. 11 34
      frontend/appflowy_tauri/src/appflowy_app/views/GridPage.tsx

File diff suppressed because it is too large
+ 266 - 204
frontend/appflowy_tauri/pnpm-lock.yaml


+ 19 - 15
frontend/appflowy_tauri/src/appflowy_app/App.tsx

@@ -1,6 +1,6 @@
 import { Routes, Route, BrowserRouter } from 'react-router-dom';
 
-import { TestColors } from './components/TestColors/TestColors';
+import { ColorPalette } from './components/tests/ColorPalette';
 import { Provider } from 'react-redux';
 import { store } from './stores/store';
 import { DocumentPage } from './views/DocumentPage';
@@ -14,6 +14,8 @@ import { ErrorHandlerPage } from './components/error/ErrorHandlerPage';
 import initializeI18n from './stores/i18n/initializeI18n';
 import { TestAPI } from './components/tests/TestAPI';
 import { GetStarted } from './components/auth/GetStarted/GetStarted';
+import { ErrorBoundary } from 'react-error-boundary';
+import { AllIcons } from '$app/components/tests/AllIcons';
 
 initializeI18n();
 
@@ -21,20 +23,22 @@ const App = () => {
   return (
     <BrowserRouter>
       <Provider store={store}>
-        <Routes>
-          <Route path={'/'} element={<ProtectedRoutes />}>
-            <Route path={'/page/colors'} element={<TestColors />} />
-            <Route path={'/page/api-test'} element={<TestAPI />} />
-            <Route path={'/page/document/:id'} element={<DocumentPage />} />
-            <Route path={'/page/board/:id'} element={<BoardPage />} />
-            <Route path={'/page/grid/:id'} element={<GridPage />} />
-          </Route>
-          <Route path={'/auth/login'} element={<LoginPage />}></Route>
-          <Route path={'/auth/getStarted'} element={<GetStarted />}></Route>
-          <Route path={'/auth/signUp'} element={<SignUpPage />}></Route>
-          <Route path={'/auth/confirm-account'} element={<ConfirmAccountPage />}></Route>
-        </Routes>
-        <ErrorHandlerPage></ErrorHandlerPage>
+        <ErrorBoundary FallbackComponent={ErrorHandlerPage}>
+          <Routes>
+            <Route path={'/'} element={<ProtectedRoutes />}>
+              <Route path={'/page/all-icons'} element={<AllIcons />} />
+              <Route path={'/page/colors'} element={<ColorPalette />} />
+              <Route path={'/page/api-test'} element={<TestAPI />} />
+              <Route path={'/page/document/:id'} element={<DocumentPage />} />
+              <Route path={'/page/board/:id'} element={<BoardPage />} />
+              <Route path={'/page/grid/:id'} element={<GridPage />} />
+            </Route>
+            <Route path={'/auth/login'} element={<LoginPage />}></Route>
+            <Route path={'/auth/getStarted'} element={<GetStarted />}></Route>
+            <Route path={'/auth/signUp'} element={<SignUpPage />}></Route>
+            <Route path={'/auth/confirm-account'} element={<ConfirmAccountPage />}></Route>
+          </Routes>
+        </ErrorBoundary>
       </Provider>
     </BrowserRouter>
   );

+ 0 - 44
frontend/appflowy_tauri/src/appflowy_app/components/TestColors/TestColors.tsx

@@ -1,44 +0,0 @@
-export const TestColors = () => {
-  return (
-    <div>
-      <h2 className={'mb-4'}>Main</h2>
-      <div className={'mb-8 flex flex-wrap items-center'}>
-        <div className={'m-2 h-[100px] w-[100px] bg-main-accent'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-main-hovered'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-main-secondary'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-main-selector'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-main-alert'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-main-warning'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-main-success'}></div>
-      </div>
-      <h2 className={'mb-4'}>Tint</h2>
-      <div className={'mb-8 flex flex-wrap items-center'}>
-        <div className={'m-2 h-[100px] w-[100px] bg-tint-1'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-tint-2'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-tint-3'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-tint-4'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-tint-5'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-tint-6'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-tint-7'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-tint-8'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-tint-9'}></div>
-      </div>
-      <h2 className={'mb-4'}>Shades</h2>
-      <div className={'mb-8 flex flex-wrap items-center'}>
-        <div className={'m-2 h-[100px] w-[100px] bg-shade-1'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-shade-2'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-shade-3'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-shade-4'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-shade-5'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-shade-6'}></div>
-      </div>
-      <h2 className={'mb-4'}>Surface</h2>
-      <div className={'mb-8 flex flex-wrap items-center'}>
-        <div className={'m-2 h-[100px] w-[100px] bg-surface-1'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-surface-2'}></div>
-        <div className={'m-2 h-[100px] w-[100px] bg-surface-3'}></div>
-        <div className={'bg-surface-4 m-2 h-[100px] w-[100px]'}></div>
-      </div>
-    </div>
-  );
-};

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

@@ -21,7 +21,7 @@ export const CellOptions = ({
     <div
       ref={ref}
       onClick={() => onClick()}
-      className={'flex flex-wrap items-center gap-2 px-4 py-2 text-xs text-black'}
+      className={'flex w-full flex-wrap items-center gap-2 px-4 py-2 text-xs text-black'}
     >
       {data?.select_options?.map((option, index) => (
         <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>

+ 42 - 38
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/CellOptionsPopup.tsx

@@ -9,10 +9,10 @@ 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 useOutsideClick from '$app/components/_shared/useOutsideClick';
 import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
 import { useAppSelector } from '$app/stores/store';
-import { ISelectOptionType } from '$app/stores/reducers/database/slice';
+import { ISelectOption, ISelectOptionType } from '$app/stores/reducers/database/slice';
+import { PopupWindow } from '$app/components/_shared/PopupWindow';
 
 export const CellOptionsPopup = ({
   top,
@@ -21,6 +21,7 @@ export const CellOptionsPopup = ({
   cellCache,
   fieldController,
   onOutsideClick,
+  openOptionDetail,
 }: {
   top: number;
   left: number;
@@ -28,27 +29,19 @@ export const CellOptionsPopup = ({
   cellCache: CellCache;
   fieldController: FieldController;
   onOutsideClick: () => void;
+  openOptionDetail: (_left: number, _top: number, _select_option: SelectOptionPB) => void;
 }) => {
-  const ref = useRef<HTMLDivElement>(null);
+  const inputRef = useRef<HTMLInputElement>(null);
   const { t } = useTranslation('');
-  const [adjustedTop, setAdjustedTop] = useState(-100);
   const [value, setValue] = useState('');
-  const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
+  const { data } = useCell(cellIdentifier, cellCache, fieldController);
   const databaseStore = useAppSelector((state) => state.database);
 
   useEffect(() => {
-    if (!ref.current) return;
-    const { height } = ref.current.getBoundingClientRect();
-    if (top + height + 40 > window.innerHeight) {
-      setAdjustedTop(window.innerHeight - height - 40);
-    } else {
-      setAdjustedTop(top);
+    if (inputRef?.current) {
+      inputRef.current.focus();
     }
-  }, [ref, window, top, left]);
-
-  useOutsideClick(ref, async () => {
-    onOutsideClick();
-  });
+  }, [inputRef]);
 
   const onKeyDown: KeyboardEventHandler = async (e) => {
     if (e.key === 'Enter' && value.length > 0) {
@@ -63,11 +56,7 @@ export const CellOptionsPopup = ({
   };
 
   const onToggleOptionClick = async (option: SelectOptionPB) => {
-    if (
-      (data as SelectOptionCellDataPB | undefined)?.select_options?.find(
-        (selectedOption) => selectedOption.id === option.id
-      )
-    ) {
+    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]);
@@ -75,23 +64,37 @@ export const CellOptionsPopup = ({
     setValue('');
   };
 
-  useEffect(() => {
-    console.log('loaded data: ', data);
-    console.log('have stored ', databaseStore.fields[cellIdentifier.fieldId]);
-  }, [data]);
+  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 (
-    <div
-      ref={ref}
-      className={`fixed z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md transition-opacity duration-300 ${
-        adjustedTop === -100 ? 'opacity-0' : 'opacity-100'
-      }`}
-      style={{ top: `${adjustedTop + 40}px`, left: `${left}px` }}
-    >
-      <div className={'flex flex-col gap-2 p-2'}>
+    <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 | undefined)?.select_options?.map((option, index) => (
+            {(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'}>
@@ -101,6 +104,7 @@ export const CellOptionsPopup = ({
             )) || ''}
           </div>
           <input
+            ref={inputRef}
             className={'py-2'}
             value={value}
             onChange={(e) => setValue(e.target.value)}
@@ -110,7 +114,7 @@ export const CellOptionsPopup = ({
           <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-semibold text-shade-3'}>{t('grid.selectOption.panelTitle') || ''}</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) => (
@@ -131,14 +135,14 @@ export const CellOptionsPopup = ({
               >
                 <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`}>{option.title}</div>
                 <div className={'flex items-center'}>
-                  {(data as SelectOptionCellDataPB | undefined)?.select_options?.find(
+                  {(data as SelectOptionCellDataPB)?.select_options?.find(
                     (selectedOption) => selectedOption.id === option.selectOptionId
                   ) && (
                     <button className={'h-5 w-5 p-1'}>
                       <CheckmarkSvg></CheckmarkSvg>
                     </button>
                   )}
-                  <button className={'h-6 w-6 p-1'}>
+                  <button onClick={(e) => onOptionDetailClick(e, option)} className={'h-6 w-6 p-1'}>
                     <Details2Svg></Details2Svg>
                   </button>
                 </div>
@@ -147,6 +151,6 @@ export const CellOptionsPopup = ({
           )}
         </div>
       </div>
-    </div>
+    </PopupWindow>
   );
 };

+ 5 - 28
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/ChangeFieldTypePopup.tsx

@@ -1,8 +1,7 @@
 import { FieldType } from '@/services/backend';
 import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
 import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
-import { useEffect, useMemo, useRef, useState } from 'react';
-import useOutsideClick from '$app/components/_shared/useOutsideClick';
+import { PopupWindow } from '$app/components/_shared/PopupWindow';
 
 const typesOrder: FieldType[] = [
   FieldType.RichText,
@@ -17,39 +16,17 @@ const typesOrder: FieldType[] = [
 
 export const ChangeFieldTypePopup = ({
   top,
-  right,
+  left,
   onClick,
   onOutsideClick,
 }: {
   top: number;
-  right: number;
+  left: number;
   onClick: (newType: FieldType) => void;
   onOutsideClick: () => void;
 }) => {
-  const ref = useRef<HTMLDivElement>(null);
-  const [adjustedTop, setAdjustedTop] = useState(-100);
-  useOutsideClick(ref, async () => {
-    onOutsideClick();
-  });
-
-  useEffect(() => {
-    if (!ref.current) return;
-    const { height } = ref.current.getBoundingClientRect();
-    if (top + height > window.innerHeight) {
-      setAdjustedTop(window.innerHeight - height);
-    } else {
-      setAdjustedTop(top);
-    }
-  }, [ref, window, top, right]);
-
   return (
-    <div
-      ref={ref}
-      className={`fixed z-10 rounded-lg bg-white p-2 text-xs shadow-md transition-opacity duration-300 ${
-        adjustedTop === -100 ? 'opacity-0' : 'opacity-100'
-      }`}
-      style={{ top: `${adjustedTop}px`, left: `${right + 30}px` }}
-    >
+    <PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
       <div className={'flex flex-col'}>
         {typesOrder.map((t, i) => (
           <button
@@ -66,6 +43,6 @@ export const ChangeFieldTypePopup = ({
           </button>
         ))}
       </div>
-    </div>
+    </PopupWindow>
   );
 };

+ 13 - 31
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/DatePickerPopup.tsx

@@ -1,15 +1,17 @@
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 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 useOutsideClick from '$app/components/_shared/useOutsideClick';
 import Calendar from 'react-calendar';
 import dayjs from 'dayjs';
 import { ClockSvg } from '$app/components/_shared/svg/ClockSvg';
 import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
 import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
 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';
 
 export const DatePickerPopup = ({
   left,
@@ -27,47 +29,27 @@ export const DatePickerPopup = ({
   onOutsideClick: () => void;
 }) => {
   const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
-  const ref = useRef<HTMLDivElement>(null);
-  const [adjustedTop, setAdjustedTop] = useState(-100);
-  // const [value, setValue] = useState();
   const { t } = useTranslation('');
   const [selectedDate, setSelectedDate] = useState<Date>(new Date());
 
   useEffect(() => {
-    if (!ref.current) return;
-    const { height } = ref.current.getBoundingClientRect();
-    if (top + height + 40 > window.innerHeight) {
-      setAdjustedTop(top - height - 40);
-    } else {
-      setAdjustedTop(top);
-    }
-  }, [ref, window, top, left]);
-
-  useOutsideClick(ref, async () => {
-    onOutsideClick();
-  });
+    const date_pb = data as DateCellDataPB | undefined;
+    if (!date_pb || !date_pb?.date.length) return;
 
-  useEffect(() => {
-    // console.log((data as DateCellDataPB).date);
-    // setSelectedDate(new Date((data as DateCellDataPB).date));
+    // should be changed after we can modify date format
+    setSelectedDate(dayjs(date_pb.date, 'MMM DD, YYYY').toDate());
   }, [data]);
 
-  const onChange = (v: Date | null | (Date | null)[]) => {
+  const onChange = async (v: Date | null | (Date | null)[]) => {
     if (v instanceof Date) {
-      console.log(dayjs(v).format('YYYY-MM-DD'));
       setSelectedDate(v);
-      // void cellController?.saveCellData(new DateCellDataPB({ date: dayjs(v).format('YYYY-MM-DD') }));
+      const date = new CalendarData(dayjs(v).add(dayjs().utcOffset(), 'minutes').toDate(), false);
+      await cellController?.saveCellData(date);
     }
   };
 
   return (
-    <div
-      ref={ref}
-      className={`fixed z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md transition-opacity duration-300 ${
-        adjustedTop === -100 ? 'opacity-0' : 'opacity-100'
-      }`}
-      style={{ top: `${adjustedTop + 40}px`, left: `${left}px` }}
-    >
+    <PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
       <div className={'px-2'}>
         <Calendar onChange={(d) => onChange(d)} value={selectedDate} />
       </div>
@@ -92,6 +74,6 @@ export const DatePickerPopup = ({
           <MoreSvg></MoreSvg>
         </i>
       </div>
-    </div>
+    </PopupWindow>
   );
 };

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

@@ -17,7 +17,7 @@ export const EditCellDate = ({
   };
 
   return (
-    <div ref={ref} onClick={() => onClick()} className={'px-4 py-2'}>
+    <div ref={ref} onClick={() => onClick()} className={'w-full px-4 py-2'}>
       {data?.date || <>&nbsp;</>}
     </div>
   );

+ 195 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellOptionPopup.tsx

@@ -0,0 +1,195 @@
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+import { KeyboardEventHandler, useEffect, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { SelectOptionColorPB, SelectOptionPB } from '@/services/backend';
+import { getBgColor } from '$app/components/_shared/getColor';
+import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
+import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
+import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
+import { PopupWindow } from '$app/components/_shared/PopupWindow';
+
+export const EditCellOptionPopup = ({
+  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,
+        color: editingSelectOption.color,
+        name: value,
+      })
+    );
+  };
+
+  const onColorClick = async (color: SelectOptionColorPB) => {
+    const svc = new SelectOptionCellBackendService(cellIdentifier);
+    await svc.updateOption(
+      new SelectOptionPB({
+        id: editingSelectOption.id,
+        color,
+        name: editingSelectOption.name,
+      })
+    );
+  };
+
+  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 className={'-mx-4 h-[1px] bg-shade-6'}></div>
+        <div className={'my-2 font-medium text-shade-3'}>{t('grid.selectOption.colorPanelTitle')}</div>
+        <div className={'flex flex-col'}>
+          <ColorItem
+            title={t('grid.selectOption.purpleColor')}
+            onClick={() => onColorClick(SelectOptionColorPB.Purple)}
+            bgColor={getBgColor(SelectOptionColorPB.Purple)}
+            checked={editingSelectOption.color === SelectOptionColorPB.Purple}
+          ></ColorItem>
+          <ColorItem
+            title={t('grid.selectOption.pinkColor')}
+            onClick={() => onColorClick(SelectOptionColorPB.Pink)}
+            bgColor={getBgColor(SelectOptionColorPB.Pink)}
+            checked={editingSelectOption.color === SelectOptionColorPB.Pink}
+          ></ColorItem>
+          <ColorItem
+            title={t('grid.selectOption.lightPinkColor')}
+            onClick={() => onColorClick(SelectOptionColorPB.LightPink)}
+            bgColor={getBgColor(SelectOptionColorPB.LightPink)}
+            checked={editingSelectOption.color === SelectOptionColorPB.LightPink}
+          ></ColorItem>
+          <ColorItem
+            title={t('grid.selectOption.orangeColor')}
+            onClick={() => onColorClick(SelectOptionColorPB.Orange)}
+            bgColor={getBgColor(SelectOptionColorPB.Orange)}
+            checked={editingSelectOption.color === SelectOptionColorPB.Orange}
+          ></ColorItem>
+          <ColorItem
+            title={t('grid.selectOption.yellowColor')}
+            onClick={() => onColorClick(SelectOptionColorPB.Yellow)}
+            bgColor={getBgColor(SelectOptionColorPB.Yellow)}
+            checked={editingSelectOption.color === SelectOptionColorPB.Yellow}
+          ></ColorItem>
+          <ColorItem
+            title={t('grid.selectOption.limeColor')}
+            onClick={() => onColorClick(SelectOptionColorPB.Lime)}
+            bgColor={getBgColor(SelectOptionColorPB.Lime)}
+            checked={editingSelectOption.color === SelectOptionColorPB.Lime}
+          ></ColorItem>
+          <ColorItem
+            title={t('grid.selectOption.greenColor')}
+            onClick={() => onColorClick(SelectOptionColorPB.Green)}
+            bgColor={getBgColor(SelectOptionColorPB.Green)}
+            checked={editingSelectOption.color === SelectOptionColorPB.Green}
+          ></ColorItem>
+          <ColorItem
+            title={t('grid.selectOption.aquaColor')}
+            onClick={() => onColorClick(SelectOptionColorPB.Aqua)}
+            bgColor={getBgColor(SelectOptionColorPB.Aqua)}
+            checked={editingSelectOption.color === SelectOptionColorPB.Aqua}
+          ></ColorItem>
+          <ColorItem
+            title={t('grid.selectOption.blueColor')}
+            onClick={() => onColorClick(SelectOptionColorPB.Blue)}
+            bgColor={getBgColor(SelectOptionColorPB.Blue)}
+            checked={editingSelectOption.color === SelectOptionColorPB.Blue}
+          ></ColorItem>
+        </div>
+      </div>
+    </PopupWindow>
+  );
+};
+
+const ColorItem = ({
+  title,
+  bgColor,
+  onClick,
+  checked,
+}: {
+  title: string;
+  bgColor: string;
+  onClick: () => void;
+  checked: boolean;
+}) => {
+  return (
+    <div
+      className={'flex cursor-pointer items-center justify-between rounded-lg p-2 hover:bg-main-secondary'}
+      onClick={() => onClick()}
+    >
+      <div className={'flex items-center gap-2'}>
+        <div className={`h-4 w-4 rounded-full ${bgColor}`}></div>
+        <span>{title}</span>
+      </div>
+      {checked && (
+        <i className={'block h-3 w-3'}>
+          <CheckmarkSvg></CheckmarkSvg>
+        </i>
+      )}
+    </div>
+  );
+};

+ 15 - 9
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCellWrapper.tsx

@@ -27,9 +27,9 @@ export const EditCellWrapper = ({
   cellIdentifier: CellIdentifier;
   cellCache: CellCache;
   fieldController: FieldController;
-  onEditFieldClick: (top: number, right: number) => void;
-  onEditOptionsClick: (left: number, top: number) => void;
-  onEditDateClick: (left: number, top: number) => void;
+  onEditFieldClick: (cell: CellIdentifier, left: number, top: number) => void;
+  onEditOptionsClick: (cell: CellIdentifier, left: number, top: number) => void;
+  onEditDateClick: (cell: CellIdentifier, left: number, top: number) => void;
 }) => {
   const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
   const databaseStore = useAppSelector((state) => state.database);
@@ -38,7 +38,7 @@ export const EditCellWrapper = ({
   const onClick = () => {
     if (!el.current) return;
     const { top, right } = el.current.getBoundingClientRect();
-    onEditFieldClick(top, right);
+    onEditFieldClick(cellIdentifier, right, top);
   };
 
   return (
@@ -70,17 +70,23 @@ export const EditCellWrapper = ({
               cellIdentifier.fieldType === FieldType.Checklist) &&
               cellController && (
                 <CellOptions
-                  data={data as SelectOptionCellDataPB | undefined}
-                  onEditClick={onEditOptionsClick}
+                  data={data as SelectOptionCellDataPB}
+                  onEditClick={(left, top) => onEditOptionsClick(cellIdentifier, left, top)}
                 ></CellOptions>
               )}
 
             {cellIdentifier.fieldType === FieldType.Checkbox && cellController && (
-              <EditCheckboxCell data={data as boolean | undefined} cellController={cellController}></EditCheckboxCell>
+              <EditCheckboxCell
+                data={data as 'Yes' | 'No' | undefined}
+                cellController={cellController}
+              ></EditCheckboxCell>
             )}
 
             {cellIdentifier.fieldType === FieldType.DateTime && (
-              <EditCellDate data={data as DateCellDataPB | undefined} onEditClick={onEditDateClick}></EditCellDate>
+              <EditCellDate
+                data={data as DateCellDataPB}
+                onEditClick={(left, top) => onEditDateClick(cellIdentifier, left, top)}
+              ></EditCellDate>
             )}
 
             {cellIdentifier.fieldType === FieldType.Number && cellController && (
@@ -88,7 +94,7 @@ export const EditCellWrapper = ({
             )}
 
             {cellIdentifier.fieldType === FieldType.URL && cellController && (
-              <EditCellUrl data={data as URLCellDataPB | undefined} cellController={cellController}></EditCellUrl>
+              <EditCellUrl data={data as URLCellDataPB} cellController={cellController}></EditCellUrl>
             )}
 
             {cellIdentifier.fieldType === FieldType.RichText && cellController && (

+ 9 - 5
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditCheckboxCell.tsx

@@ -1,22 +1,26 @@
 import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
 import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
-import { CellController } from '$app/stores/effects/database/cell/cell_controller';
+import { CheckboxCellController } from '$app/stores/effects/database/cell/controller_builder';
 
 export const EditCheckboxCell = ({
   data,
   cellController,
 }: {
-  data: boolean | undefined;
-  cellController: CellController<any, any>;
+  data: 'Yes' | 'No' | undefined;
+  cellController: CheckboxCellController;
 }) => {
   const toggleValue = async () => {
-    await cellController?.saveCellData(!data);
+    if (data === 'Yes') {
+      await cellController?.saveCellData('No');
+    } else {
+      await cellController?.saveCellData('Yes');
+    }
   };
 
   return (
     <div onClick={() => toggleValue()} className={'block px-4 py-2'}>
       <button className={'h-5 w-5'}>
-        {data ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
+        {data === 'Yes' ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
       </button>
     </div>
   );

+ 12 - 28
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditFieldPopup.tsx

@@ -1,5 +1,4 @@
 import { useEffect, useRef, useState } from 'react';
-import useOutsideClick from '$app/components/_shared/useOutsideClick';
 import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
 import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
 import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
@@ -10,10 +9,11 @@ import { FieldInfo } from '$app/stores/effects/database/field/field_controller';
 import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
 import { useAppSelector } from '$app/stores/store';
 import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+import { PopupWindow } from '$app/components/_shared/PopupWindow';
 
 export const EditFieldPopup = ({
   top,
-  right,
+  left,
   cellIdentifier,
   viewId,
   onOutsideClick,
@@ -21,7 +21,7 @@ export const EditFieldPopup = ({
   changeFieldTypeClick,
 }: {
   top: number;
-  right: number;
+  left: number;
   cellIdentifier: CellIdentifier;
   viewId: string;
   onOutsideClick: () => void;
@@ -30,31 +30,13 @@ export const EditFieldPopup = ({
 }) => {
   const databaseStore = useAppSelector((state) => state.database);
   const { t } = useTranslation('');
-  const ref = useRef<HTMLDivElement>(null);
   const changeTypeButtonRef = useRef<HTMLDivElement>(null);
   const [name, setName] = useState('');
 
-  const [adjustedTop, setAdjustedTop] = useState(-100);
-
-  useOutsideClick(ref, async () => {
-    await save();
-    onOutsideClick();
-  });
-
   useEffect(() => {
     setName(databaseStore.fields[cellIdentifier.fieldId].title);
   }, [databaseStore, cellIdentifier]);
 
-  useEffect(() => {
-    if (!ref.current) return;
-    const { height } = ref.current.getBoundingClientRect();
-    if (top + height > window.innerHeight) {
-      setAdjustedTop(window.innerHeight - height);
-    } else {
-      setAdjustedTop(top);
-    }
-  }, [ref, window, top, right]);
-
   const save = async () => {
     if (!fieldInfo) return;
     const controller = new TypeOptionController(viewId, Some(fieldInfo));
@@ -78,12 +60,14 @@ export const EditFieldPopup = ({
   };
 
   return (
-    <div
-      ref={ref}
-      className={`fixed z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md transition-opacity duration-300 ${
-        adjustedTop === -100 ? 'opacity-0' : 'opacity-100'
-      }`}
-      style={{ top: `${adjustedTop}px`, left: `${right + 10}px` }}
+    <PopupWindow
+      className={'px-2 py-2 text-xs'}
+      onOutsideClick={async () => {
+        await save();
+        onOutsideClick();
+      }}
+      left={left}
+      top={top}
     >
       <div className={'flex flex-col gap-2 p-2'}>
         <input
@@ -125,6 +109,6 @@ export const EditFieldPopup = ({
           </i>
         </div>
       </div>
-    </div>
+    </PopupWindow>
   );
 };

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

@@ -11,10 +11,11 @@ import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
 import { ChangeFieldTypePopup } from '$app/components/_shared/EditRow/ChangeFieldTypePopup';
 import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
 import { Some } from 'ts-results';
-import { FieldType } from '@/services/backend';
+import { FieldType, SelectOptionPB } from '@/services/backend';
 import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPopup';
 import { DatePickerPopup } from '$app/components/_shared/EditRow/DatePickerPopup';
 import { DragDropContext, Droppable, OnDragEndResponder } from 'react-beautiful-dnd';
+import { EditCellOptionPopup } from '$app/components/_shared/EditRow/EditCellOptionPopup';
 
 export const EditRow = ({
   onClose,
@@ -34,11 +35,11 @@ export const EditRow = ({
   const [editingCell, setEditingCell] = useState<CellIdentifier | null>(null);
   const [showFieldEditor, setShowFieldEditor] = useState(false);
   const [editFieldTop, setEditFieldTop] = useState(0);
-  const [editFieldRight, setEditFieldRight] = useState(0);
+  const [editFieldLeft, setEditFieldLeft] = useState(0);
 
   const [showChangeFieldTypePopup, setShowChangeFieldTypePopup] = useState(false);
   const [changeFieldTypeTop, setChangeFieldTypeTop] = useState(0);
-  const [changeFieldTypeRight, setChangeFieldTypeRight] = useState(0);
+  const [changeFieldTypeLeft, setChangeFieldTypeLeft] = useState(0);
 
   const [showChangeOptionsPopup, setShowChangeOptionsPopup] = useState(false);
   const [changeOptionsTop, setChangeOptionsTop] = useState(0);
@@ -48,6 +49,12 @@ export const EditRow = ({
   const [datePickerTop, setDatePickerTop] = useState(0);
   const [datePickerLeft, setDatePickerLeft] = useState(0);
 
+  const [showEditCellOption, setShowEditCellOption] = useState(false);
+  const [editCellOptionTop, setEditCellOptionTop] = useState(0);
+  const [editCellOptionLeft, setEditCellOptionLeft] = useState(0);
+
+  const [editingSelectOption, setEditingSelectOption] = useState<SelectOptionPB | undefined>();
+
   useEffect(() => {
     setUnveil(true);
   }, []);
@@ -59,10 +66,10 @@ export const EditRow = ({
     }, 300);
   };
 
-  const onEditFieldClick = (cellIdentifier: CellIdentifier, top: number, right: number) => {
+  const onEditFieldClick = (cellIdentifier: CellIdentifier, left: number, top: number) => {
     setEditingCell(cellIdentifier);
     setEditFieldTop(top);
-    setEditFieldRight(right);
+    setEditFieldLeft(left + 10);
     setShowFieldEditor(true);
   };
 
@@ -74,7 +81,7 @@ export const EditRow = ({
 
   const onChangeFieldTypeClick = (buttonTop: number, buttonRight: number) => {
     setChangeFieldTypeTop(buttonTop);
-    setChangeFieldTypeRight(buttonRight);
+    setChangeFieldTypeLeft(buttonRight + 30);
     setShowChangeFieldTypePopup(true);
   };
 
@@ -95,17 +102,24 @@ export const EditRow = ({
   const onEditOptionsClick = async (cellIdentifier: CellIdentifier, left: number, top: number) => {
     setEditingCell(cellIdentifier);
     setChangeOptionsLeft(left);
-    setChangeOptionsTop(top);
+    setChangeOptionsTop(top + 40);
     setShowChangeOptionsPopup(true);
   };
 
   const onEditDateClick = async (cellIdentifier: CellIdentifier, left: number, top: number) => {
     setEditingCell(cellIdentifier);
     setDatePickerLeft(left);
-    setDatePickerTop(top);
+    setDatePickerTop(top + 40);
     setShowDatePicker(true);
   };
 
+  const onOpenOptionDetailClick = (_left: number, _top: number, _select_option: SelectOptionPB) => {
+    setEditingSelectOption(_select_option);
+    setShowEditCellOption(true);
+    setEditCellOptionLeft(_left);
+    setEditCellOptionTop(_top);
+  };
+
   const onDragEnd: OnDragEndResponder = (result) => {
     if (!result.destination?.index) return;
     void controller.moveField({
@@ -120,8 +134,14 @@ export const EditRow = ({
       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 className={`relative flex h-[90%] w-[70%] flex-col gap-8 rounded-xl bg-white px-8 pb-4 pt-12`}>
+      <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`}
+      >
         <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>
@@ -145,11 +165,9 @@ export const EditRow = ({
                     cellIdentifier={cell.cellIdentifier}
                     cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
                     fieldController={controller.fieldController}
-                    onEditFieldClick={(top: number, right: number) => onEditFieldClick(cell.cellIdentifier, top, right)}
-                    onEditOptionsClick={(left: number, top: number) =>
-                      onEditOptionsClick(cell.cellIdentifier, left, top)
-                    }
-                    onEditDateClick={(left: number, top: number) => onEditDateClick(cell.cellIdentifier, left, top)}
+                    onEditFieldClick={onEditFieldClick}
+                    onEditOptionsClick={onEditOptionsClick}
+                    onEditDateClick={onEditDateClick}
                   ></EditCellWrapper>
                 ))}
               </div>
@@ -172,7 +190,7 @@ export const EditRow = ({
         {showFieldEditor && editingCell && (
           <EditFieldPopup
             top={editFieldTop}
-            right={editFieldRight}
+            left={editFieldLeft}
             cellIdentifier={editingCell}
             viewId={viewId}
             onOutsideClick={onOutsideEditFieldClick}
@@ -183,7 +201,7 @@ export const EditRow = ({
         {showChangeFieldTypePopup && (
           <ChangeFieldTypePopup
             top={changeFieldTypeTop}
-            right={changeFieldTypeRight}
+            left={changeFieldTypeLeft}
             onClick={(newType) => changeFieldType(newType)}
             onOutsideClick={() => setShowChangeFieldTypePopup(false)}
           ></ChangeFieldTypePopup>
@@ -196,6 +214,7 @@ export const EditRow = ({
             cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
             fieldController={controller.fieldController}
             onOutsideClick={() => setShowChangeOptionsPopup(false)}
+            openOptionDetail={onOpenOptionDetailClick}
           ></CellOptionsPopup>
         )}
         {showDatePicker && editingCell && (
@@ -208,6 +227,17 @@ export const EditRow = ({
             onOutsideClick={() => setShowDatePicker(false)}
           ></DatePickerPopup>
         )}
+        {showEditCellOption && editingCell && editingSelectOption && (
+          <EditCellOptionPopup
+            top={editCellOptionTop}
+            left={editCellOptionLeft}
+            cellIdentifier={editingCell}
+            editingSelectOption={editingSelectOption}
+            onOutsideClick={() => {
+              setShowEditCellOption(false);
+            }}
+          ></EditCellOptionPopup>
+        )}
       </div>
     </div>
   );

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

@@ -1,4 +1,4 @@
-import { IPopupItem, Popup } from './Popup';
+import { IPopupItem, PopupSelect } from './PopupSelect';
 import i18n from 'i18next';
 
 const supportedLanguages: { key: string; title: string }[] = [
@@ -37,11 +37,11 @@ export const LanguageSelectPopup = ({ onClose }: { onClose: () => void }) => {
     icon: <></>,
   }));
   return (
-    <Popup
+    <PopupSelect
       items={items}
       className={'absolute top-full right-0 z-10 w-[200px]'}
       onOutsideClick={onClose}
       columns={2}
-    ></Popup>
+    ></PopupSelect>
   );
 };

+ 10 - 8
frontend/appflowy_tauri/src/appflowy_app/components/_shared/Popup.tsx → frontend/appflowy_tauri/src/appflowy_app/components/_shared/PopupSelect.tsx

@@ -2,12 +2,12 @@ import { MouseEvent, ReactNode, useRef } from 'react';
 import useOutsideClick from './useOutsideClick';
 
 export interface IPopupItem {
-  icon: ReactNode;
+  icon: ReactNode | (() => JSX.Element);
   title: string;
   onClick: () => void;
 }
 
-export const Popup = ({
+export const PopupSelect = ({
   items,
   className = '',
   onOutsideClick,
@@ -31,18 +31,20 @@ export const Popup = ({
   return (
     <div ref={ref} className={`${className} rounded-lg bg-white px-2 py-2 shadow-md`} style={style}>
       <div
-        className={`grid ${columns === 1 && 'grid-cols-1'} ${columns === 2 && 'grid-cols-2'} ${
-          columns === 3 && 'grid-cols-3'
-        } gap-x-4`}
+        className={
+          (columns === 2 ? 'grid grid-cols-2' : '') + (columns === 3 ? 'grid grid-cols-3' : '') + ' w-full gap-x-4'
+        }
       >
         {items.map((item, index) => (
           <button
             key={index}
-            className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 hover:bg-main-secondary'}
+            className={'flex w-full cursor-pointer items-center gap-2 rounded-lg px-2 py-2 hover:bg-main-secondary'}
             onClick={(e) => handleClick(e, item)}
           >
-            {item.icon}
-            <span className={'flex-shrink-0'}>{item.title}</span>
+            <>
+              {typeof item.icon === 'function' ? item.icon() : item.icon}
+              <span className={'flex-shrink-0'}>{item.title}</span>
+            </>
           </button>
         ))}
       </div>

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

@@ -0,0 +1,51 @@
+import { ReactNode, useEffect, useRef, useState } from 'react';
+import useOutsideClick from '$app/components/_shared/useOutsideClick';
+
+export const PopupWindow = ({
+  children,
+  className,
+  onOutsideClick,
+  left,
+  top,
+}: {
+  children: ReactNode;
+  className: string;
+  onOutsideClick: () => void;
+  left: number;
+  top: number;
+}) => {
+  const ref = useRef<HTMLDivElement>(null);
+  useOutsideClick(ref, onOutsideClick);
+
+  const [adjustedTop, setAdjustedTop] = useState(-100);
+  const [adjustedLeft, setAdjustedLeft] = useState(-100);
+
+  useEffect(() => {
+    if (!ref.current) return;
+    const { height, width } = ref.current.getBoundingClientRect();
+    if (top + height > window.innerHeight) {
+      setAdjustedTop(window.innerHeight - height);
+    } else {
+      setAdjustedTop(top);
+    }
+    if (left + width > window.innerWidth) {
+      setAdjustedLeft(window.innerWidth - width);
+    } else {
+      setAdjustedLeft(left);
+    }
+  }, [ref, left, top, window]);
+
+  return (
+    <div
+      ref={ref}
+      className={
+        'fixed z-10 rounded-lg bg-white shadow-md transition-opacity duration-300 ' +
+        (adjustedTop === -100 && adjustedLeft === -100 ? 'opacity-0 ' : 'opacity-100 ') +
+        (className || '')
+      }
+      style={{ top: `${adjustedTop}px`, left: `${adjustedLeft}px` }}
+    >
+      {children}
+    </div>
+  );
+};

+ 9 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/useDatabase.ts

@@ -56,7 +56,15 @@ export const useDatabase = (viewId: string, type?: ViewLayoutPB) => {
           void loadFields(fieldInfos);
         },
       });
-      await controller.open();
+
+      const openResult = await controller.open();
+      if (openResult.ok) {
+        setRows(
+          openResult.val.map((pb) => {
+            return new RowInfo(viewId, controller.fieldController.fieldInfos, pb);
+          })
+        );
+      }
 
       if (type === ViewLayoutPB.Board) {
         const fieldId = await controller.getGroupByFieldId();

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EyeClosedSvg.tsx

@@ -1,4 +1,4 @@
-export const EyeClosed = () => {
+export const EyeClosedSvg = () => {
   return (
     <svg width='100%' height='100%' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
       <path

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/EyeOpenSvg.tsx

@@ -1,4 +1,4 @@
-export const EyeOpened = () => {
+export const EyeOpenSvg = () => {
   return (
     <svg width='100%' height='100%' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
       <path

+ 10 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/FullView.tsx

@@ -0,0 +1,10 @@
+export const FullView = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <path d='M6 13H3V10' stroke='#333333' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M10 3H13V6' stroke='#333333' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M3 13L7 9' stroke='#333333' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M13 3L9 7' stroke='#333333' strokeLinecap='round' strokeLinejoin='round' />
+    </svg>
+  );
+};

+ 26 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/GroupByFieldSvg.tsx

@@ -0,0 +1,26 @@
+export const GroupByFieldSvg = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <path
+        d='M10 2H13C13.5523 2 14 2.44772 14 3V6'
+        stroke='currentColor'
+        strokeLinecap='round'
+        strokeLinejoin='round'
+      />
+      <path d='M6 2H3C2.44772 2 2 2.44772 2 3V6' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
+      <path
+        d='M6 14H3C2.44772 14 2 13.5523 2 13V10'
+        stroke='currentColor'
+        strokeLinecap='round'
+        strokeLinejoin='round'
+      />
+      <path
+        d='M10 14H13C13.5523 14 14 13.5523 14 13V10'
+        stroke='currentColor'
+        strokeLinecap='round'
+        strokeLinejoin='round'
+      />
+      <rect x='6' y='6' width='4' height='4' rx='1' stroke='currentColor' />
+    </svg>
+  );
+};

+ 11 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/GroupBySvg.tsx

@@ -0,0 +1,11 @@
+export const GroupBySvg = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <path d='M10 2H13C13.5523 2 14 2.44772 14 3V6' stroke='#333333' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M6 2H3C2.44772 2 2 2.44772 2 3V6' stroke='#333333' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M6 14H3C2.44772 14 2 13.5523 2 13V10' stroke='#333333' strokeLinecap='round' strokeLinejoin='round' />
+      <path d='M10 14H13C13.5523 14 14 13.5523 14 13V10' stroke='#333333' strokeLinecap='round' strokeLinejoin='round' />
+      <rect x='6' y='6' width='4' height='4' rx='1' stroke='#333333' />
+    </svg>
+  );
+};

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

@@ -1,6 +1,6 @@
 import { AppflowyLogo } from '../../_shared/svg/AppflowyLogo';
-import { EyeClosed } from '../../_shared/svg/EyeClosedSvg';
-import { EyeOpened } from '../../_shared/svg/EyeOpenSvg';
+import { EyeClosedSvg } from '../../_shared/svg/EyeClosedSvg';
+import { EyeOpenSvg } from '../../_shared/svg/EyeOpenSvg';
 import { useLogin } from './Login.hooks';
 import { Link } from 'react-router-dom';
 import { Button } from '../../_shared/Button';
@@ -55,7 +55,7 @@ export const Login = () => {
                 className='absolute right-0 top-0 flex h-full w-12 items-center justify-center '
                 onClick={onTogglePassword}
               >
-                <span className='h-6 w-6'>{showPassword ? <EyeClosed /> : <EyeOpened />}</span>
+                <span className='h-6 w-6'>{showPassword ? <EyeClosedSvg /> : <EyeOpenSvg />}</span>
               </button>
             </div>
 

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

@@ -1,6 +1,6 @@
 import { AppflowyLogo } from '../../_shared/svg/AppflowyLogo';
-import { EyeClosed } from '../../_shared/svg/EyeClosedSvg';
-import { EyeOpened } from '../../_shared/svg/EyeOpenSvg';
+import { EyeClosedSvg } from '../../_shared/svg/EyeClosedSvg';
+import { EyeOpenSvg } from '../../_shared/svg/EyeOpenSvg';
 
 import { useSignUp } from './SignUp.hooks';
 import { Link } from 'react-router-dom';
@@ -71,7 +71,7 @@ export const SignUp = () => {
               onClick={onTogglePassword}
               type='button'
             >
-              <span className='h-6 w-6'>{showPassword ? <EyeClosed /> : <EyeOpened />}</span>
+              <span className='h-6 w-6'>{showPassword ? <EyeClosedSvg /> : <EyeOpenSvg />}</span>
             </button>
           </div>
 
@@ -89,7 +89,7 @@ export const SignUp = () => {
               onClick={onToggleConfirmPassword}
               type='button'
             >
-              <span className='h-6 w-6'>{showConfirmPassword ? <EyeClosed /> : <EyeOpened />}</span>
+              <span className='h-6 w-6'>{showConfirmPassword ? <EyeClosedSvg /> : <EyeOpenSvg />}</span>
             </button>
           </div>
         </div>

+ 5 - 10
frontend/appflowy_tauri/src/appflowy_app/components/board/Board.tsx

@@ -1,6 +1,5 @@
-import { SettingsSvg } from '../_shared/svg/SettingsSvg';
 import { SearchInput } from '../_shared/SearchInput';
-import { BoardBlock } from './BoardBlock';
+import { BoardGroup } from './BoardGroup';
 import { NewBoardBlock } from './NewBoardBlock';
 import { useDatabase } from '../_shared/database-hooks/useDatabase';
 import { ViewLayoutPB } from '@/services/backend';
@@ -8,8 +7,9 @@ import { DragDropContext } from 'react-beautiful-dnd';
 import { useState } from 'react';
 import { RowInfo } from '$app/stores/effects/database/row/row_cache';
 import { EditRow } from '$app/components/_shared/EditRow/EditRow';
+import { BoardToolbar } from '$app/components/board/BoardToolbar';
 
-export const Board = ({ viewId }: { viewId: string }) => {
+export const Board = ({ viewId, title }: { viewId: string; title: string }) => {
   const { controller, rows, groups, groupByFieldId, onNewRowClick, onDragEnd } = useDatabase(viewId, ViewLayoutPB.Board);
   const [showBoardRow, setShowBoardRow] = useState(false);
   const [boardRowInfo, setBoardRowInfo] = useState<RowInfo>();
@@ -22,12 +22,7 @@ export const Board = ({ viewId }: { viewId: string }) => {
   return (
     <>
       <div className='flex w-full items-center justify-between'>
-        <div className={'flex items-center text-xl font-semibold'}>
-          <div>{'Kanban'}</div>
-          <button className={'ml-2 h-5 w-5'}>
-            <SettingsSvg></SettingsSvg>
-          </button>
-        </div>
+        <BoardToolbar title={title} />
 
         <div className='flex shrink-0 items-center gap-4'>
           <SearchInput />
@@ -39,7 +34,7 @@ export const Board = ({ viewId }: { viewId: string }) => {
             {controller &&
               groups &&
               groups.map((group, index) => (
-                <BoardBlock
+                <BoardGroup
                   key={group.groupId}
                   viewId={viewId}
                   controller={controller}

+ 9 - 3
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCard.tsx

@@ -1,9 +1,10 @@
 import { Details2Svg } from '../_shared/svg/Details2Svg';
-import { RowInfo } from '../../stores/effects/database/row/row_cache';
+import { RowInfo } from '$app/stores/effects/database/row/row_cache';
 import { useRow } from '../_shared/database-hooks/useRow';
-import { DatabaseController } from '../../stores/effects/database/database_controller';
+import { DatabaseController } from '$app/stores/effects/database/database_controller';
 import { BoardCell } from './BoardCell';
 import { Draggable } from 'react-beautiful-dnd';
+import { MouseEventHandler } from 'react';
 
 export const BoardCard = ({
   index,
@@ -22,6 +23,11 @@ export const BoardCard = ({
 }) => {
   const { cells } = useRow(viewId, controller, rowInfo);
 
+  const onDetailClick: MouseEventHandler = (e) => {
+    e.stopPropagation();
+    // onOpenRow(rowInfo);
+  };
+
   return (
     <Draggable draggableId={rowInfo.row.id} index={index}>
       {(provided) => (
@@ -32,7 +38,7 @@ export const BoardCard = ({
           onClick={() => onOpenRow(rowInfo)}
           className={`relative cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2 transition-transform duration-100 hover:bg-main-selector `}
         >
-          <button className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
+          <button onClick={onDetailClick} className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
             <Details2Svg></Details2Svg>
           </button>
           <div className={'flex flex-col gap-3'}>

+ 11 - 4
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCell.tsx

@@ -1,11 +1,12 @@
-import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
-import { CellCache } from '../../stores/effects/database/cell/cell_cache';
-import { FieldController } from '../../stores/effects/database/field/field_controller';
-import { FieldType } from '../../../services/backend';
+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 { FieldType } from '@/services/backend';
 import { BoardOptionsCell } from './BoardOptionsCell';
 import { BoardDateCell } from './BoardDateCell';
 import { BoardTextCell } from './BoardTextCell';
 import { BoardUrlCell } from '$app/components/board/BoardUrlCell';
+import { BoardCheckboxCell } from '$app/components/board/BoardCheckboxCell';
 
 export const BoardCell = ({
   cellIdentifier,
@@ -38,6 +39,12 @@ export const BoardCell = ({
           cellCache={cellCache}
           fieldController={fieldController}
         ></BoardUrlCell>
+      ) : cellIdentifier.fieldType === FieldType.Checkbox ? (
+        <BoardCheckboxCell
+          cellIdentifier={cellIdentifier}
+          cellCache={cellCache}
+          fieldController={fieldController}
+        ></BoardCheckboxCell>
       ) : (
         <BoardTextCell
           cellIdentifier={cellIdentifier}

+ 23 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardCheckboxCell.tsx

@@ -0,0 +1,23 @@
+import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
+import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
+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';
+
+export const BoardCheckboxCell = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const { data } = useCell(cellIdentifier, cellCache, fieldController);
+  return (
+    <i className={'h-5 w-5'}>
+      {data === 'Yes' ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
+    </i>
+  );
+};

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

@@ -1,8 +1,8 @@
-import { DateCellDataPB } from '../../../services/backend';
+import { DateCellDataPB } from '@/services/backend';
 import { useCell } from '../_shared/database-hooks/useCell';
-import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
-import { CellCache } from '../../stores/effects/database/cell/cell_cache';
-import { FieldController } from '../../stores/effects/database/field/field_controller';
+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';
 
 export const BoardDateCell = ({
   cellIdentifier,

+ 35 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardFieldsPopup.tsx

@@ -0,0 +1,35 @@
+import { useAppSelector } from '$app/stores/store';
+import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
+import { useRef } from 'react';
+import useOutsideClick from '$app/components/_shared/useOutsideClick';
+import { EyeOpenSvg } from '$app/components/_shared/svg/EyeOpenSvg';
+
+export const BoardFieldsPopup = ({ hidePopup }: { hidePopup: () => void }) => {
+  const columns = useAppSelector((state) => state.database.columns);
+  const fields = useAppSelector((state) => state.database.fields);
+  const ref = useRef<HTMLDivElement>(null);
+  useOutsideClick(ref, () => hidePopup());
+
+  return (
+    <div ref={ref} className={'absolute top-full left-full z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md'}>
+      {columns.map((column, index) => (
+        <div
+          className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-main-secondary'}
+          key={index}
+        >
+          <div className={'flex items-center gap-2 '}>
+            <i className={'flex h-5 w-5 flex-shrink-0 items-center justify-center'}>
+              <FieldTypeIcon fieldType={fields[column.fieldId].fieldType}></FieldTypeIcon>
+            </i>
+            <span className={'flex-shrink-0'}>{fields[column.fieldId].title}</span>
+          </div>
+          <div className={'ml-12'}>
+            <i className={'block h-5 w-5'}>
+              <EyeOpenSvg></EyeOpenSvg>
+            </i>
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+};

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardBlock.tsx → frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroup.tsx

@@ -1,12 +1,12 @@
 import { Details2Svg } from '../_shared/svg/Details2Svg';
 import AddSvg from '../_shared/svg/AddSvg';
 import { BoardCard } from './BoardCard';
-import { RowInfo } from '../../stores/effects/database/row/row_cache';
-import { DatabaseController } from '../../stores/effects/database/database_controller';
+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';
 
-export const BoardBlock = ({
+export const BoardGroup = ({
   viewId,
   controller,
   allRows,

+ 35 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardGroupFieldsPopup.tsx

@@ -0,0 +1,35 @@
+import { useAppSelector } from '$app/stores/store';
+import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
+import { useRef } from 'react';
+import useOutsideClick from '$app/components/_shared/useOutsideClick';
+import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
+
+export const BoardGroupFieldsPopup = ({ hidePopup }: { hidePopup: () => void }) => {
+  const columns = useAppSelector((state) => state.database.columns);
+  const fields = useAppSelector((state) => state.database.fields);
+  const ref = useRef<HTMLDivElement>(null);
+  useOutsideClick(ref, () => hidePopup());
+
+  return (
+    <div ref={ref} className={'absolute top-full left-full z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md'}>
+      {columns.map((column, index) => (
+        <div
+          className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-main-secondary'}
+          key={index}
+        >
+          <div className={'flex items-center gap-2 '}>
+            <i className={'flex h-5 w-5 flex-shrink-0 items-center justify-center'}>
+              <FieldTypeIcon fieldType={fields[column.fieldId].fieldType}></FieldTypeIcon>
+            </i>
+            <span className={'flex-shrink-0'}>{fields[column.fieldId].title}</span>
+          </div>
+          <div className={'ml-12'}>
+            <i className={'block h-3 w-3'}>
+              <CheckmarkSvg></CheckmarkSvg>
+            </i>
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+};

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

@@ -1,8 +1,8 @@
-import { SelectOptionCellDataPB } from '../../../services/backend';
+import { SelectOptionCellDataPB } from '@/services/backend';
 import { useCell } from '../_shared/database-hooks/useCell';
-import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
-import { CellCache } from '../../stores/effects/database/cell/cell_cache';
-import { FieldController } from '../../stores/effects/database/field/field_controller';
+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 { getBgColor } from '$app/components/_shared/getColor';
 
 export const BoardOptionsCell = ({
@@ -18,7 +18,7 @@ export const BoardOptionsCell = ({
 
   return (
     <div className={'flex flex-wrap items-center gap-2 py-2 text-xs text-black'}>
-      {(data as SelectOptionCellDataPB | undefined)?.select_options?.map((option, index) => (
+      {(data as SelectOptionCellDataPB)?.select_options?.map((option, index) => (
         <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
           {option?.name || ''}
         </div>

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

@@ -0,0 +1,48 @@
+import { useEffect, useState } from 'react';
+import { PropertiesSvg } from '$app/components/_shared/svg/PropertiesSvg';
+import { IPopupItem, PopupSelect } from '$app/components/_shared/PopupSelect';
+import { useTranslation } from 'react-i18next';
+import { GroupByFieldSvg } from '$app/components/_shared/svg/GroupByFieldSvg';
+
+export const BoardSettingsPopup = ({
+  hidePopup,
+  onFieldsClick,
+  onGroupClick,
+}: {
+  hidePopup: () => void;
+  onFieldsClick: () => void;
+  onGroupClick: () => void;
+}) => {
+  const [settingsItems, setSettingsItems] = useState<IPopupItem[]>([]);
+  const { t } = useTranslation('');
+  useEffect(() => {
+    setSettingsItems([
+      {
+        icon: (
+          <i className={'h-5 w-5'}>
+            <PropertiesSvg></PropertiesSvg>
+          </i>
+        ),
+        title: t('grid.settings.Properties'),
+        onClick: onFieldsClick,
+      },
+      {
+        icon: (
+          <i className={'h-5 w-5'}>
+            <GroupByFieldSvg></GroupByFieldSvg>
+          </i>
+        ),
+        title: t('grid.settings.group'),
+        onClick: onGroupClick,
+      },
+    ]);
+  }, [t]);
+
+  return (
+    <PopupSelect
+      onOutsideClick={() => hidePopup()}
+      items={settingsItems}
+      className={'absolute top-full left-full z-10 text-xs'}
+    ></PopupSelect>
+  );
+};

+ 37 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardToolbar.hooks.ts

@@ -0,0 +1,37 @@
+import { useState } from 'react';
+
+export const useBoardToolbar = () => {
+  const [showSettings, setShowSettings] = useState(false);
+  const [showAllFields, setShowAllFields] = useState(false);
+  const [showGroupFields, setShowGroupFields] = useState(false);
+
+  const onSettingsClick = () => {
+    setShowSettings(!showSettings);
+  };
+
+  const onFieldsClick = () => {
+    setShowSettings(false);
+    setShowAllFields(true);
+  };
+
+  const onGroupClick = () => {
+    setShowSettings(false);
+    setShowGroupFields(true);
+  };
+
+  const hidePopup = () => {
+    setShowSettings(false);
+    setShowAllFields(false);
+    setShowGroupFields(false);
+  };
+
+  return {
+    showSettings,
+    onSettingsClick,
+    onFieldsClick,
+    onGroupClick,
+    hidePopup,
+    showAllFields,
+    showGroupFields,
+  };
+};

+ 28 - 0
frontend/appflowy_tauri/src/appflowy_app/components/board/BoardToolbar.tsx

@@ -0,0 +1,28 @@
+import { SettingsSvg } from '$app/components/_shared/svg/SettingsSvg';
+import { useBoardToolbar } from '$app/components/board/BoardToolbar.hooks';
+import { BoardSettingsPopup } from '$app/components/board/BoardSettingsPopup';
+import { BoardFieldsPopup } from '$app/components/board/BoardFieldsPopup';
+import { BoardGroupFieldsPopup } from '$app/components/board/BoardGroupFieldsPopup';
+
+export const BoardToolbar = ({ title }: { title: string }) => {
+  const { showSettings, showAllFields, showGroupFields, onSettingsClick, onFieldsClick, onGroupClick, hidePopup } =
+    useBoardToolbar();
+
+  return (
+    <div className={'relative flex items-center gap-2'}>
+      <div className={'text-xl font-semibold'}>{title}</div>
+      <button onClick={() => onSettingsClick()} className={'h-5 w-5'}>
+        <SettingsSvg></SettingsSvg>
+      </button>
+      {showSettings && (
+        <BoardSettingsPopup
+          hidePopup={hidePopup}
+          onFieldsClick={onFieldsClick}
+          onGroupClick={onGroupClick}
+        ></BoardSettingsPopup>
+      )}
+      {showAllFields && <BoardFieldsPopup hidePopup={hidePopup}></BoardFieldsPopup>}
+      {showGroupFields && <BoardGroupFieldsPopup hidePopup={hidePopup}></BoardGroupFieldsPopup>}
+    </div>
+  );
+};

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

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

+ 7 - 1
frontend/appflowy_tauri/src/appflowy_app/components/error/Error.hooks.ts

@@ -2,7 +2,7 @@ import { useAppDispatch, useAppSelector } from '../../stores/store';
 import { errorActions } from '../../stores/reducers/error/slice';
 import { useEffect, useState } from 'react';
 
-export const useError = () => {
+export const useError = (e: Error) => {
   const dispatch = useAppDispatch();
   const error = useAppSelector((state) => state.error);
   const [errorMessage, setErrorMessage] = useState('');
@@ -13,6 +13,12 @@ export const useError = () => {
     setErrorMessage(error.message);
   }, [error]);
 
+  useEffect(() => {
+    if (e) {
+      showError(e.message);
+    }
+  }, [e]);
+
   const showError = (msg: string) => {
     dispatch(errorActions.showError(msg));
   };

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/error/ErrorHandlerPage.tsx

@@ -1,8 +1,8 @@
 import { useError } from './Error.hooks';
 import { ErrorModal } from './ErrorModal';
 
-export const ErrorHandlerPage = () => {
-  const { hideError, errorMessage, displayError } = useError();
+export const ErrorHandlerPage = ({ error }: { error: Error }) => {
+  const { hideError, errorMessage, displayError } = useError(error);
 
   return displayError ? <ErrorModal message={errorMessage} onClose={hideError}></ErrorModal> : <></>;
 };

+ 57 - 0
frontend/appflowy_tauri/src/appflowy_app/components/grid/Grid/Grid.tsx

@@ -0,0 +1,57 @@
+import { useDatabase } from '$app/components/_shared/database-hooks/useDatabase';
+import { GridTableCount } from '../GridTableCount/GridTableCount';
+import { GridTableHeader } from '../GridTableHeader/GridTableHeader';
+import { GridAddRow } from '../GridTableRows/GridAddRow';
+import { GridTableRows } from '../GridTableRows/GridTableRows';
+import { GridTitle } from '../GridTitle/GridTitle';
+import { GridToolbar } from '../GridToolbar/GridToolbar';
+import { EditRow } from '$app/components/_shared/EditRow/EditRow';
+import { useState } from 'react';
+import { RowInfo } from '$app/stores/effects/database/row/row_cache';
+import { ViewLayoutPB } from '@/services/backend';
+
+export const Grid = ({ viewId }: { viewId: string }) => {
+  const { controller, rows, groups } = useDatabase(viewId, ViewLayoutPB.Grid);
+  const [showGridRow, setShowGridRow] = useState(false);
+  const [boardRowInfo, setBoardRowInfo] = useState<RowInfo>();
+
+  const onOpenRow = (rowInfo: RowInfo) => {
+    setBoardRowInfo(rowInfo);
+    setShowGridRow(true);
+  };
+
+  return (
+    <>
+      {controller && groups && (
+        <>
+          <div className='mx-auto mt-8 flex flex-col gap-8 px-8'>
+            <div className='flex w-full  items-center justify-between'>
+              <GridTitle />
+              <GridToolbar />
+            </div>
+
+            {/* table component view with text area for td */}
+            <div className='flex flex-col gap-4'>
+              <table className='w-full table-fixed text-sm'>
+                <GridTableHeader controller={controller} />
+                <GridTableRows onOpenRow={onOpenRow} allRows={rows} viewId={viewId} controller={controller} />
+              </table>
+
+              <GridAddRow controller={controller} />
+            </div>
+
+            <GridTableCount />
+          </div>
+          {showGridRow && boardRowInfo && (
+            <EditRow
+              onClose={() => setShowGridRow(false)}
+              viewId={viewId}
+              controller={controller}
+              rowInfo={boardRowInfo}
+            ></EditRow>
+          )}
+        </>
+      )}
+    </>
+  );
+};

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

@@ -1,4 +1,3 @@
-import { Link } from 'react-router-dom';
 import AddSvg from '../../_shared/svg/AddSvg';
 
 export const GridAddView = () => {

+ 44 - 0
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridCell/GridCell.tsx

@@ -0,0 +1,44 @@
+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 { FieldType } from '@/services/backend';
+import GridSingleSelectOptions from './GridSingleSelectOptions';
+import GridTextCell from './GridTextCell';
+import { GridCheckBox } from './GridCheckBox';
+import { GridDate } from './GridDate';
+import { GridUrl } from './GridUrl';
+import { GridNumberCell } from './GridNumberCell';
+
+export const GridCell = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  return (
+    <>
+      {cellIdentifier.fieldType === FieldType.MultiSelect ||
+      cellIdentifier.fieldType === FieldType.Checklist ||
+      cellIdentifier.fieldType === FieldType.SingleSelect ? (
+        <GridSingleSelectOptions
+          cellIdentifier={cellIdentifier}
+          cellCache={cellCache}
+          fieldController={fieldController}
+        />
+      ) : cellIdentifier.fieldType === FieldType.Checkbox ? (
+        <GridCheckBox cellIdentifier={cellIdentifier} cellCache={cellCache} fieldController={fieldController} />
+      ) : cellIdentifier.fieldType === FieldType.DateTime ? (
+        <GridDate cellIdentifier={cellIdentifier} cellCache={cellCache} fieldController={fieldController}></GridDate>
+      ) : cellIdentifier.fieldType === FieldType.URL ? (
+        <GridUrl cellIdentifier={cellIdentifier} cellCache={cellCache} fieldController={fieldController}></GridUrl>
+      ) : cellIdentifier.fieldType === FieldType.Number ? (
+        <GridNumberCell cellIdentifier={cellIdentifier} cellCache={cellCache} fieldController={fieldController} />
+      ) : (
+        <GridTextCell cellIdentifier={cellIdentifier} cellCache={cellCache} fieldController={fieldController} />
+      )}
+    </>
+  );
+};

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

@@ -0,0 +1,23 @@
+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 { useCell } from '../../_shared/database-hooks/useCell';
+
+export const GridCheckBox = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
+
+  return (
+    <div className='flex w-full justify-start'>
+      {cellController && <EditCheckboxCell cellController={cellController} data={data as 'Yes' | 'No' | undefined} />}
+    </div>
+  );
+};

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

@@ -0,0 +1,47 @@
+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 '../../_shared/database-hooks/useCell';
+import { DateCellDataPB } from '@/services/backend';
+import { EditCellDate } from '../../_shared/EditRow/EditCellDate';
+import { useState } from 'react';
+import { DatePickerPopup } from '../../_shared/EditRow/DatePickerPopup';
+
+export const GridDate = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
+
+  const [showDatePopup, setShowDatePopup] = useState(false);
+  const [datePickerTop, setdatePickerTop] = useState(0);
+  const [datePickerLeft, setdatePickerLeft] = useState(0);
+
+  const onEditDateClick = async (left: number, top: number) => {
+    setdatePickerLeft(left);
+    setdatePickerTop(top);
+    setShowDatePopup(true);
+  };
+
+  return (
+    <div className='flex w-full cursor-pointer justify-start'>
+      {cellController && <EditCellDate data={data as DateCellDataPB} onEditClick={onEditDateClick}></EditCellDate>}
+
+      {showDatePopup && (
+        <DatePickerPopup
+          top={datePickerTop}
+          left={datePickerLeft}
+          cellIdentifier={cellIdentifier}
+          cellCache={cellCache}
+          fieldController={fieldController}
+          onOutsideClick={() => setShowDatePopup(false)}
+        ></DatePickerPopup>
+      )}
+    </div>
+  );
+};

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

@@ -0,0 +1,25 @@
+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 '../../_shared/database-hooks/useCell';
+import { EditCellNumber } from '../../_shared/EditRow/EditCellNumber';
+
+export const GridNumberCell = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
+
+  return (
+    <div className='w-full'>
+      {cellController && (
+        <EditCellNumber data={data as string | undefined} cellController={cellController}></EditCellNumber>
+      )}
+    </div>
+  );
+};

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

@@ -0,0 +1,75 @@
+import { useState } from 'react';
+import { CellOptions } from '$app/components/_shared/EditRow/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 { SelectOptionCellDataPB, SelectOptionPB } from '@/services/backend/models/flowy-database/select_type_option';
+import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPopup';
+import { EditCellOptionPopup } from '$app/components/_shared/EditRow/EditCellOptionPopup';
+
+export default function GridSingleSelectOptions({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) {
+  const { data } = useCell(cellIdentifier, cellCache, fieldController);
+
+  const [showOptionsPopup, setShowOptionsPopup] = useState(false);
+  const [changeOptionsTop, setChangeOptionsTop] = useState(0);
+  const [changeOptionsLeft, setChangeOptionsLeft] = useState(0);
+
+  const [showEditCellOption, setShowEditCellOption] = useState(false);
+  const [editCellOptionTop, setEditCellOptionTop] = useState(0);
+  const [editCellOptionLeft, setEditCellOptionLeft] = useState(0);
+
+  const [editingSelectOption, setEditingSelectOption] = useState<SelectOptionPB | undefined>();
+
+  const onEditOptionsClick = async (left: number, top: number) => {
+    setChangeOptionsLeft(left);
+    setChangeOptionsTop(top);
+    setShowOptionsPopup(true);
+  };
+
+  const onOpenOptionDetailClick = (_left: number, _top: number, _select_option: SelectOptionPB) => {
+    setEditingSelectOption(_select_option);
+    setShowEditCellOption(true);
+    setEditCellOptionLeft(_left);
+    setEditCellOptionTop(_top);
+  };
+
+  return (
+    <>
+      <div className='flex w-full cursor-pointer justify-start'>
+        <CellOptions data={data as SelectOptionCellDataPB} onEditClick={onEditOptionsClick} />
+      </div>
+
+      {showOptionsPopup && (
+        <CellOptionsPopup
+          top={changeOptionsTop}
+          left={changeOptionsLeft}
+          cellIdentifier={cellIdentifier}
+          cellCache={cellCache}
+          fieldController={fieldController}
+          onOutsideClick={() => setShowOptionsPopup(false)}
+          openOptionDetail={onOpenOptionDetailClick}
+        />
+      )}
+      {showEditCellOption && editingSelectOption && (
+        <EditCellOptionPopup
+          top={editCellOptionTop}
+          left={editCellOptionLeft}
+          cellIdentifier={cellIdentifier}
+          editingSelectOption={editingSelectOption}
+          onOutsideClick={() => {
+            setShowEditCellOption(false);
+          }}
+        ></EditCellOptionPopup>
+      )}
+    </>
+  );
+}

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

@@ -0,0 +1,23 @@
+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 '../../_shared/database-hooks/useCell';
+import { EditCellText } from '../../_shared/EditRow/EditCellText';
+
+export default function GridTextCell({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) {
+  const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
+
+  return (
+    <div className='w-full'>
+      {cellController && <EditCellText data={data as string | undefined} cellController={cellController}></EditCellText>}
+    </div>
+  );
+}

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

@@ -0,0 +1,22 @@
+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 '../../_shared/database-hooks/useCell';
+import { EditCellUrl } from '../../_shared/EditRow/EditCellUrl';
+import { URLCellDataPB } from '@/services/backend/models/flowy-database/url_type_option_entities';
+
+export const GridUrl = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
+
+  return (
+    <>{cellController && <EditCellUrl data={data as URLCellDataPB} cellController={cellController}></EditCellUrl>}</>
+  );
+};

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableCount/GridTableCount.hooks.ts

@@ -1,4 +1,4 @@
-import { useAppSelector } from '../../../stores/store';
+import { useAppSelector } from '$app/stores/store';
 
 export const useGridTableCount = () => {
   const { grid } = useAppSelector((state) => state);

+ 17 - 19
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeader.hooks.tsx

@@ -1,27 +1,25 @@
-import { nanoid } from 'nanoid';
-import { FieldType } from '@/services/backend/models/flowy-database/field_entities';
-import { gridActions } from '../../../stores/reducers/grid/slice';
-import { useAppDispatch, useAppSelector } from '../../../stores/store';
+import { useAppSelector } from '$app/stores/store';
+import { DatabaseController } from '@/appflowy_app/stores/effects/database/database_controller';
+import { TypeOptionController } from '@/appflowy_app/stores/effects/database/field/type_option/type_option_controller';
+import { None } from 'ts-results';
 
-export const useGridTableHeaderHooks = function () {
-  const dispatch = useAppDispatch();
-  const grid = useAppSelector((state) => state.grid);
+export const useGridTableHeaderHooks = function (controller: DatabaseController) {
+  const database = useAppSelector((state) => state.database);
 
-  const onAddField = () => {
-    dispatch(
-      gridActions.addField({
-        field: {
-          fieldId: nanoid(8),
-          name: 'Name',
-          fieldOptions: {},
-          fieldType: FieldType.RichText,
-        },
-      })
-    );
+  const onAddField = async () => {
+    // TODO: move this to database controller hook
+    const fieldController = new TypeOptionController(controller.viewId, None);
+    await fieldController.initialize();
   };
 
   return {
-    fields: grid.fields,
+    fields: Object.values(database.fields).map((field) => {
+      return {
+        fieldId: field.fieldId,
+        name: field.title,
+        fieldType: field.fieldType,
+      };
+    }),
     onAddField,
   };
 };

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

@@ -1,49 +1,32 @@
+import { DatabaseController } from '@/appflowy_app/stores/effects/database/database_controller';
+
 import AddSvg from '../../_shared/svg/AddSvg';
 import { useGridTableHeaderHooks } from './GridTableHeader.hooks';
-import { TextTypeSvg } from '../../_shared/svg/TextTypeSvg';
-import { NumberTypeSvg } from '../../_shared/svg/NumberTypeSvg';
-import { DateTypeSvg } from '../../_shared/svg/DateTypeSvg';
-import { SingleSelectTypeSvg } from '../../_shared/svg/SingleSelectTypeSvg';
-import { MultiSelectTypeSvg } from '../../_shared/svg/MultiSelectTypeSvg';
-import { ChecklistTypeSvg } from '../../_shared/svg/ChecklistTypeSvg';
-import { UrlTypeSvg } from '../../_shared/svg/UrlTypeSvg';
-import { FieldType } from '@/services/backend/models/flowy-database/field_entities';
 
-export const GridTableHeader = () => {
-  const { fields, onAddField } = useGridTableHeaderHooks();
+import { GridTableHeaderItem } from './GridTableHeaderItem';
+import { useTranslation } from 'react-i18next';
+
+export const GridTableHeader = ({ controller }: { controller: DatabaseController }) => {
+  const { fields, onAddField } = useGridTableHeaderHooks(controller);
+  const { t } = useTranslation('');
 
   return (
     <>
       <thead>
         <tr>
           {fields.map((field, i) => {
-            return (
-              <th key={field.fieldId} className='m-0 border border-l-0 border-shade-6 p-0'>
-                <div className={'flex cursor-pointer items-center p-2 hover:bg-main-secondary'}>
-                  <i className={'mr-2 h-5 w-5 text-shade-3'}>
-                    {field.fieldType === FieldType.RichText && <TextTypeSvg></TextTypeSvg>}
-                    {field.fieldType === FieldType.Number && <NumberTypeSvg></NumberTypeSvg>}
-                    {field.fieldType === FieldType.DateTime && <DateTypeSvg></DateTypeSvg>}
-                    {field.fieldType === FieldType.SingleSelect && <SingleSelectTypeSvg></SingleSelectTypeSvg>}
-                    {field.fieldType === FieldType.MultiSelect && <MultiSelectTypeSvg></MultiSelectTypeSvg>}
-                    {field.fieldType === FieldType.Checklist && <ChecklistTypeSvg></ChecklistTypeSvg>}
-                    {field.fieldType === FieldType.URL && <UrlTypeSvg></UrlTypeSvg>}
-                  </i>
-                  <span>{field.name}</span>
-                </div>
-              </th>
-            );
+            return <GridTableHeaderItem field={field} controller={controller} key={i} />;
           })}
 
           <th className='m-0 w-40 border border-r-0 border-shade-6 p-0'>
             <div
-              className='flex cursor-pointer items-center p-2 text-shade-3 hover:bg-main-secondary hover:text-black'
+              className='flex cursor-pointer items-center px-4 py-2 text-shade-3 hover:bg-main-secondary hover:text-black'
               onClick={onAddField}
             >
               <i className='mr-2 h-5 w-5'>
                 <AddSvg />
               </i>
-              <span>New column</span>
+              <span>{t('grid.field.newColumn')}</span>
             </div>
           </th>
         </tr>

+ 123 - 0
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableHeader/GridTableHeaderItem.tsx

@@ -0,0 +1,123 @@
+import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell_bd_svc';
+import { DatabaseController } from '@/appflowy_app/stores/effects/database/database_controller';
+import { TypeOptionController } from '@/appflowy_app/stores/effects/database/field/type_option/type_option_controller';
+import { FieldType } from '@/services/backend';
+import { useState, useRef } from 'react';
+import { Some } from 'ts-results';
+import { ChangeFieldTypePopup } from '../../_shared/EditRow/ChangeFieldTypePopup';
+import { EditFieldPopup } from '../../_shared/EditRow/EditFieldPopup';
+import { ChecklistTypeSvg } from '../../_shared/svg/ChecklistTypeSvg';
+import { DateTypeSvg } from '../../_shared/svg/DateTypeSvg';
+import { MultiSelectTypeSvg } from '../../_shared/svg/MultiSelectTypeSvg';
+import { NumberTypeSvg } from '../../_shared/svg/NumberTypeSvg';
+import { SingleSelectTypeSvg } from '../../_shared/svg/SingleSelectTypeSvg';
+import { TextTypeSvg } from '../../_shared/svg/TextTypeSvg';
+import { UrlTypeSvg } from '../../_shared/svg/UrlTypeSvg';
+
+export const GridTableHeaderItem = ({
+  controller,
+  field,
+}: {
+  controller: DatabaseController;
+  field: {
+    fieldId: string;
+    name: string;
+    fieldType: FieldType;
+  };
+}) => {
+  const [showFieldEditor, setShowFieldEditor] = useState(false);
+  const [editFieldTop, setEditFieldTop] = useState(0);
+  const [editFieldRight, setEditFieldRight] = useState(0);
+
+  const [showChangeFieldTypePopup, setShowChangeFieldTypePopup] = useState(false);
+  const [changeFieldTypeTop, setChangeFieldTypeTop] = useState(0);
+  const [changeFieldTypeRight, setChangeFieldTypeRight] = useState(0);
+
+  const [editingField, setEditingField] = useState<{
+    fieldId: string;
+    name: string;
+    fieldType: FieldType;
+  } | null>(null);
+
+  const ref = useRef<HTMLDivElement>(null);
+
+  const changeFieldType = async (newType: FieldType) => {
+    if (!editingField) return;
+
+    const currentField = controller.fieldController.getField(editingField.fieldId);
+    if (!currentField) return;
+
+    const typeOptionController = new TypeOptionController(controller.viewId, Some(currentField));
+    await typeOptionController.switchToField(newType);
+
+    setEditingField({
+      ...editingField,
+      fieldType: newType,
+    });
+
+    setShowChangeFieldTypePopup(false);
+  };
+
+  return (
+    <th key={field.fieldId} className='m-0 border border-l-0 border-shade-6  p-0'>
+      <div
+        className={'flex w-full cursor-pointer items-center px-4 py-2 hover:bg-main-secondary'}
+        ref={ref}
+        onClick={() => {
+          if (!ref.current) return;
+          const { top, left } = ref.current.getBoundingClientRect();
+
+          setEditFieldRight(left - 10);
+          setEditFieldTop(top + 35);
+          setEditingField(field);
+          setShowFieldEditor(true);
+        }}
+      >
+        <i className={'mr-2 h-5 w-5 text-shade-3'}>
+          {field.fieldType === FieldType.RichText && <TextTypeSvg></TextTypeSvg>}
+          {field.fieldType === FieldType.Number && <NumberTypeSvg></NumberTypeSvg>}
+          {field.fieldType === FieldType.DateTime && <DateTypeSvg></DateTypeSvg>}
+          {field.fieldType === FieldType.SingleSelect && <SingleSelectTypeSvg></SingleSelectTypeSvg>}
+          {field.fieldType === FieldType.MultiSelect && <MultiSelectTypeSvg></MultiSelectTypeSvg>}
+          {field.fieldType === FieldType.Checklist && <ChecklistTypeSvg></ChecklistTypeSvg>}
+          {field.fieldType === FieldType.Checkbox && <ChecklistTypeSvg></ChecklistTypeSvg>}
+          {field.fieldType === FieldType.URL && <UrlTypeSvg></UrlTypeSvg>}
+        </i>
+        <span>{field.name}</span>
+
+        {showFieldEditor && editingField && (
+          <EditFieldPopup
+            top={editFieldTop}
+            left={editFieldRight}
+            cellIdentifier={
+              {
+                fieldId: editingField.fieldId,
+                fieldType: editingField.fieldType,
+                viewId: controller.viewId,
+              } as CellIdentifier
+            }
+            viewId={controller.viewId}
+            onOutsideClick={() => {
+              setShowFieldEditor(false);
+            }}
+            fieldInfo={controller.fieldController.getField(editingField.fieldId)}
+            changeFieldTypeClick={(buttonTop, buttonRight) => {
+              setChangeFieldTypeTop(buttonTop);
+              setChangeFieldTypeRight(buttonRight);
+              setShowChangeFieldTypePopup(true);
+            }}
+          ></EditFieldPopup>
+        )}
+
+        {showChangeFieldTypePopup && (
+          <ChangeFieldTypePopup
+            top={changeFieldTypeTop}
+            left={changeFieldTypeRight}
+            onClick={(newType) => changeFieldType(newType)}
+            onOutsideClick={() => setShowChangeFieldTypePopup(false)}
+          ></ChangeFieldTypePopup>
+        )}
+      </div>
+    </th>
+  );
+};

+ 4 - 7
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridAddRow.hooks.ts

@@ -1,11 +1,8 @@
-import { gridActions } from '../../../stores/reducers/grid/slice';
-import { useAppDispatch } from '../../../stores/store';
+import { DatabaseController } from '@/appflowy_app/stores/effects/database/database_controller';
 
-export const useGridAddRow = () => {
-  const dispatch = useAppDispatch();
-
-  function addRow() {
-    dispatch(gridActions.addRow());
+export const useGridAddRow = (controller: DatabaseController) => {
+  async function addRow() {
+    await controller.createRow();
   }
 
   return {

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

@@ -1,7 +1,10 @@
+import { DatabaseController } from '@/appflowy_app/stores/effects/database/database_controller';
 import AddSvg from '../../_shared/svg/AddSvg';
 import { useGridAddRow } from './GridAddRow.hooks';
-export const GridAddRow = () => {
-  const { addRow } = useGridAddRow();
+import { useTranslation } from 'react-i18next';
+export const GridAddRow = ({ controller }: { controller: DatabaseController }) => {
+  const { addRow } = useGridAddRow(controller);
+  const { t } = useTranslation('');
 
   return (
     <div>
@@ -9,7 +12,7 @@ export const GridAddRow = () => {
         <i className='mr-2 h-5 w-5'>
           <AddSvg />
         </i>
-        <span>New row</span>
+        <span>{t('grid.row.newRow')}</span>
       </button>
     </div>
   );

+ 16 - 0
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableCell.tsx

@@ -0,0 +1,16 @@
+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 { GridCell } from '../GridCell/GridCell';
+
+export const GridTableCell = ({
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  return <GridCell cellIdentifier={cellIdentifier} cellCache={cellCache} fieldController={fieldController} />;
+};

+ 0 - 28
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableItem.hooks.ts

@@ -1,28 +0,0 @@
-import { useState } from 'react';
-import { gridActions } from '../../../stores/reducers/grid/slice';
-import { useAppDispatch, useAppSelector } from '../../../stores/store';
-
-export const useGridTableItemHooks = (
-  rowItem: { value: string | number; fieldId: string; cellId: string },
-  rowId: string
-) => {
-  const dispatch = useAppDispatch();
-  const [value, setValue] = useState(rowItem.value);
-
-  function onValueChange(event: React.ChangeEvent<HTMLInputElement>) {
-    setValue(event.target.value);
-  }
-
-  function onValueBlur() {
-    dispatch(gridActions.updateRowValue({ rowId: rowId, cellId: rowItem.cellId, value }));
-  }
-
-  const grid = useAppSelector((state) => state.grid);
-
-  return {
-    rows: grid.rows,
-    onValueChange,
-    value,
-    onValueBlur,
-  };
-};

+ 0 - 26
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableItem.tsx

@@ -1,26 +0,0 @@
-import { useGridTableItemHooks } from './GridTableItem.hooks';
-
-export const GridTableItem = ({
-  rowItem,
-  rowId,
-}: {
-  rowItem: {
-    fieldId: string;
-    value: string | number;
-    cellId: string;
-  };
-  rowId: string;
-}) => {
-  const { value, onValueChange, onValueBlur } = useGridTableItemHooks(rowItem, rowId);
-  return (
-    <div>
-      <input
-        className='h-full w-full rounded-lg border border-transparent p-2 hover:border-main-accent'
-        type='text'
-        value={value}
-        onChange={onValueChange}
-        onBlur={onValueBlur}
-      />
-    </div>
-  );
-};

+ 46 - 0
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableRow.tsx

@@ -0,0 +1,46 @@
+import { DatabaseController } from '@/appflowy_app/stores/effects/database/database_controller';
+import { RowInfo } from '@/appflowy_app/stores/effects/database/row/row_cache';
+import { useRow } from '../../_shared/database-hooks/useRow';
+import { FullView } from '../../_shared/svg/FullView';
+import { GridCell } from '../GridCell/GridCell';
+
+export const GridTableRow = ({
+  viewId,
+  controller,
+  row,
+  onOpenRow,
+}: {
+  viewId: string;
+  controller: DatabaseController;
+  row: RowInfo;
+  onOpenRow: (rowId: RowInfo) => void;
+}) => {
+  const { cells } = useRow(viewId, controller, row);
+
+  return (
+    <tr className='group'>
+      {cells.map((cell, cellIndex) => {
+        return (
+          <td className='m-0  border border-l-0 border-shade-6 p-0 ' key={cellIndex}>
+            <div className='flex w-full items-center justify-end'>
+              <GridCell
+                cellIdentifier={cell.cellIdentifier}
+                cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
+                fieldController={controller.fieldController}
+              />
+
+              {cellIndex === 0 && (
+                <div
+                  onClick={() => onOpenRow(row)}
+                  className='mr-1 hidden h-9 w-9  cursor-pointer rounded p-2 hover:bg-slate-200 group-hover:block '
+                >
+                  <FullView />
+                </div>
+              )}
+            </div>
+          </td>
+        );
+      })}
+    </tr>
+  );
+};

+ 0 - 9
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableRows.hooks.ts

@@ -1,9 +0,0 @@
-import { useAppSelector } from '../../../stores/store';
-
-export const useGridTableRowsHooks = () => {
-  const grid = useAppSelector((state) => state.grid);
-
-  return {
-    rows: grid.rows,
-  };
-};

+ 16 - 19
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTableRows/GridTableRows.tsx

@@ -1,24 +1,21 @@
-import { GridTableItem } from './GridTableItem';
-import { useGridTableRowsHooks } from './GridTableRows.hooks';
-
-export const GridTableRows = () => {
-  const { rows } = useGridTableRowsHooks();
+import { DatabaseController } from '@/appflowy_app/stores/effects/database/database_controller';
+import { RowInfo } from '@/appflowy_app/stores/effects/database/row/row_cache';
+import { GridTableRow } from './GridTableRow';
+export const GridTableRows = ({
+  viewId,
+  controller,
+  allRows,
+  onOpenRow,
+}: {
+  viewId: string;
+  controller: DatabaseController;
+  allRows: readonly RowInfo[];
+  onOpenRow: (rowId: RowInfo) => void;
+}) => {
   return (
     <tbody>
-      {rows.map((row, i) => {
-        return (
-          <tr key={row.rowId}>
-            {row.values.map((value) => {
-              return (
-                <td key={value.fieldId} className='m-0 border border-l-0 border-shade-6 p-0'>
-                  <GridTableItem rowItem={value} rowId={row.rowId} />
-                </td>
-              );
-            })}
-
-            <td className='m-0 border border-r-0 border-shade-6 p-0'></td>
-          </tr>
-        );
+      {allRows.map((row, i) => {
+        return <GridTableRow onOpenRow={onOpenRow} row={row} key={i} viewId={viewId} controller={controller} />;
       })}
     </tbody>
   );

+ 4 - 9
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTitle/GridTitle.hooks.ts

@@ -1,7 +1,5 @@
+import { useAppDispatch, useAppSelector } from '@/appflowy_app/stores/store';
 import { useState } from 'react';
-import { gridActions } from '../../../stores/reducers/grid/slice';
-
-import { useAppDispatch, useAppSelector } from '../../../stores/store';
 
 export const useGridTitleHooks = function () {
   const dispatch = useAppDispatch();
@@ -9,16 +7,12 @@ export const useGridTitleHooks = function () {
 
   const [title, setTitle] = useState(grid.title);
   const [changingTitle, setChangingTitle] = useState(false);
+  const [showOptions, setShowOptions] = useState(false);
 
   const onTitleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
     setTitle(event.target.value);
   };
 
-  const onTitleBlur = () => {
-    dispatch(gridActions.updateGridTitle({ title }));
-    setChangingTitle(false);
-  };
-
   const onTitleClick = () => {
     setChangingTitle(true);
   };
@@ -26,8 +20,9 @@ export const useGridTitleHooks = function () {
   return {
     title,
     onTitleChange,
-    onTitleBlur,
     onTitleClick,
     changingTitle,
+    showOptions,
+    setShowOptions,
   };
 };

+ 11 - 5
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTitle/GridTitle.tsx

@@ -1,15 +1,21 @@
 import { useGridTitleHooks } from './GridTitle.hooks';
 import { SettingsSvg } from '../../_shared/svg/SettingsSvg';
+import { GridTitleOptionsPopup } from './GridTitleOptionsPopup';
 
 export const GridTitle = () => {
-  const { title } = useGridTitleHooks();
+  const { title, showOptions, setShowOptions } = useGridTitleHooks();
 
   return (
-    <div className={'flex items-center text-xl font-semibold'}>
+    <div className={'relative flex items-center '}>
       <div>{title}</div>
-      <button className={'ml-2 h-5 w-5'}>
-        <SettingsSvg></SettingsSvg>
-      </button>
+
+      <div className='flex '>
+        <button className={'ml-2 h-5 w-5 '} onClick={() => setShowOptions(!showOptions)}>
+          <SettingsSvg></SettingsSvg>
+        </button>
+
+        {showOptions && <GridTitleOptionsPopup onClose={() => setShowOptions(!showOptions)} />}
+      </div>
     </div>
   );
 };

+ 55 - 0
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridTitle/GridTitleOptionsPopup.tsx

@@ -0,0 +1,55 @@
+import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect';
+import { FilterSvg } from '../../_shared/svg/FilterSvg';
+import { GroupBySvg } from '../../_shared/svg/GroupBySvg';
+import { PropertiesSvg } from '../../_shared/svg/PropertiesSvg';
+import { SortSvg } from '../../_shared/svg/SortSvg';
+
+export const GridTitleOptionsPopup = ({ onClose }: { onClose?: () => void }) => {
+  const items: IPopupItem[] = [
+    {
+      icon: (
+        <i className={'h-[16px] w-[16px] text-black'}>
+          <FilterSvg />
+        </i>
+      ),
+      onClick: () => {
+        console.log('filter');
+      },
+      title: 'Filter',
+    },
+    {
+      icon: (
+        <i className={'h-[16px] w-[16px] text-black'}>
+          <SortSvg />
+        </i>
+      ),
+      onClick: () => {
+        console.log('sort');
+      },
+      title: 'Sort',
+    },
+    {
+      icon: (
+        <i className={'h-[16px] w-[16px] text-black'}>
+          <PropertiesSvg />
+        </i>
+      ),
+      onClick: () => {
+        console.log('fields');
+      },
+      title: 'Fields',
+    },
+    {
+      icon: (
+        <i className={'h-[16px] w-[16px] text-black'}>
+          <GroupBySvg />
+        </i>
+      ),
+      onClick: () => {
+        console.log('group by');
+      },
+      title: 'Group by',
+    },
+  ];
+  return <PopupSelect items={items} className={'absolute top-full z-10 w-fit'} onOutsideClick={onClose} />;
+};

+ 0 - 6
frontend/appflowy_tauri/src/appflowy_app/components/grid/GridToolbar/GridToolbar.tsx

@@ -1,17 +1,11 @@
 import { GridAddView } from '../GridAddView/GridAddView';
 import { SearchInput } from '../../_shared/SearchInput';
-import { GridSortButton } from './GridSortButton';
-import { GridFieldsButton } from './GridFieldsButton';
-import { GridFilterButton } from './GridFilterButton';
 
 export const GridToolbar = () => {
   return (
     <div className='flex shrink-0 items-center gap-4'>
       <SearchInput />
       <GridAddView />
-      <GridFilterButton></GridFilterButton>
-      <GridSortButton></GridSortButton>
-      <GridFieldsButton></GridFieldsButton>
     </div>
   );
 };

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/OptionsPopup.tsx

@@ -1,4 +1,4 @@
-import { IPopupItem, Popup } from '../../_shared/Popup';
+import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect';
 import { LogoutSvg } from '../../_shared/svg/LogoutSvg';
 
 export const OptionsPopup = ({ onSignOutClick, onClose }: { onSignOutClick: () => void; onClose: () => void }) => {
@@ -14,10 +14,10 @@ export const OptionsPopup = ({ onSignOutClick, onClose }: { onSignOutClick: () =
     },
   ];
   return (
-    <Popup
+    <PopupSelect
       className={'absolute top-[50px] right-[30px] z-10 whitespace-nowrap'}
       items={items}
       onOutsideClick={onClose}
-    ></Popup>
+    ></PopupSelect>
   );
 };

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

@@ -5,7 +5,7 @@ import { IPage, pagesActions } from '../../../stores/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 { useError } from '../../error/Error.hooks';
+
 import { AppObserver } from '../../../stores/effects/folder/app/app_observer';
 import { useNavigate } from 'react-router-dom';
 import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants';
@@ -32,9 +32,6 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
   const appBackendService = new AppBackendService(folder.id);
   const workspaceBackendService = new WorkspaceBackendService(workspace.id || '');
 
-  // Error
-  const error = useError();
-
   useEffect(() => {
     void appObserver.subscribe({
       onAppChanged: (change) => {
@@ -85,12 +82,8 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
   };
 
   const changeFolderTitle = async (newTitle: string) => {
-    try {
-      await appBackendService.update({ name: newTitle });
-      appDispatch(foldersActions.renameFolder({ id: folder.id, newTitle }));
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    await appBackendService.update({ name: newTitle });
+    appDispatch(foldersActions.renameFolder({ id: folder.id, newTitle }));
   };
 
   const closeRenamePopup = () => {
@@ -99,24 +92,16 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
 
   const deleteFolder = async () => {
     closePopup();
-    try {
-      await appBackendService.delete();
-      appDispatch(foldersActions.deleteFolder({ id: folder.id }));
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    await appBackendService.delete();
+    appDispatch(foldersActions.deleteFolder({ id: folder.id }));
   };
 
   const duplicateFolder = async () => {
     closePopup();
-    try {
-      const newApp = await workspaceBackendService.createApp({
-        name: folder.title,
-      });
-      appDispatch(foldersActions.addFolder({ id: newApp.id, title: folder.title }));
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    const newApp = await workspaceBackendService.createApp({
+      name: folder.title,
+    });
+    appDispatch(foldersActions.addFolder({ id: newApp.id, title: folder.title }));
   };
 
   const closePopup = () => {
@@ -126,77 +111,65 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
 
   const onAddNewDocumentPage = async () => {
     closePopup();
-    try {
-      const newView = await appBackendService.createView({
-        name: 'New Document 1',
-        layoutType: ViewLayoutPB.Document,
-      });
-
-      appDispatch(
-        pagesActions.addPage({
-          folderId: folder.id,
-          pageType: ViewLayoutPB.Document,
-          title: newView.name,
-          id: newView.id,
-        })
-      );
-
-      setShowPages(true);
-
-      navigate(`/page/document/${newView.id}`);
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    const newView = await appBackendService.createView({
+      name: 'New Document 1',
+      layoutType: ViewLayoutPB.Document,
+    });
+
+    appDispatch(
+      pagesActions.addPage({
+        folderId: folder.id,
+        pageType: ViewLayoutPB.Document,
+        title: newView.name,
+        id: newView.id,
+      })
+    );
+
+    setShowPages(true);
+
+    navigate(`/page/document/${newView.id}`);
   };
 
   const onAddNewBoardPage = async () => {
     closePopup();
-    try {
-      const newView = await appBackendService.createView({
-        name: 'New Board 1',
-        layoutType: ViewLayoutPB.Board,
-      });
-
-      setShowPages(true);
-
-      appDispatch(
-        pagesActions.addPage({
-          folderId: folder.id,
-          pageType: ViewLayoutPB.Board,
-          title: newView.name,
-          id: newView.id,
-        })
-      );
-
-      navigate(`/page/board/${newView.id}`);
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    const newView = await appBackendService.createView({
+      name: 'New Board 1',
+      layoutType: ViewLayoutPB.Board,
+    });
+
+    setShowPages(true);
+
+    appDispatch(
+      pagesActions.addPage({
+        folderId: folder.id,
+        pageType: ViewLayoutPB.Board,
+        title: newView.name,
+        id: newView.id,
+      })
+    );
+
+    navigate(`/page/board/${newView.id}`);
   };
 
   const onAddNewGridPage = async () => {
     closePopup();
-    try {
-      const newView = await appBackendService.createView({
-        name: 'New Grid 1',
-        layoutType: ViewLayoutPB.Grid,
-      });
-
-      setShowPages(true);
-
-      appDispatch(
-        pagesActions.addPage({
-          folderId: folder.id,
-          pageType: ViewLayoutPB.Grid,
-          title: newView.name,
-          id: newView.id,
-        })
-      );
-
-      navigate(`/page/grid/${newView.id}`);
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    const newView = await appBackendService.createView({
+      name: 'New Grid 1',
+      layoutType: ViewLayoutPB.Grid,
+    });
+
+    setShowPages(true);
+
+    appDispatch(
+      pagesActions.addPage({
+        folderId: folder.id,
+        pageType: ViewLayoutPB.Grid,
+        title: newView.name,
+        id: newView.id,
+      })
+    );
+
+    navigate(`/page/grid/${newView.id}`);
   };
 
   useEffect(() => {

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx

@@ -1,4 +1,4 @@
-import { IPopupItem, Popup } from '../../_shared/Popup';
+import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect';
 import { EditSvg } from '../../_shared/svg/EditSvg';
 import { TrashSvg } from '../../_shared/svg/TrashSvg';
 import { CopySvg } from '../../_shared/svg/CopySvg';
@@ -47,11 +47,11 @@ export const NavItemOptionsPopup = ({
   ];
 
   return (
-    <Popup
+    <PopupSelect
       onOutsideClick={() => onClose && onClose()}
       items={items}
       className={`absolute right-0`}
       style={{ top: `${top}px` }}
-    ></Popup>
+    ></PopupSelect>
   );
 };

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

@@ -117,6 +117,7 @@ export const NavigationPanel = ({
             {/*<PluginsButton></PluginsButton>*/}
 
             <DesignSpec></DesignSpec>
+            <AllIcons></AllIcons>
             <TestBackendButton></TestBackendButton>
 
             {/*Trash Button*/}
@@ -158,7 +159,7 @@ export const TestBackendButton = () => {
       onClick={() => navigate('/page/api-test')}
       className={'flex w-full items-center rounded-lg px-4 py-2 hover:bg-surface-2'}
     >
-      APITest
+      API Test
     </button>
   );
 };
@@ -171,7 +172,19 @@ export const DesignSpec = () => {
       onClick={() => navigate('page/colors')}
       className={'flex w-full items-center rounded-lg px-4 py-2 hover:bg-surface-2'}
     >
-      Design Specs
+      Color Palette
+    </button>
+  );
+};
+
+export const AllIcons = () => {
+  const navigate = useNavigate();
+  return (
+    <button
+      onClick={() => navigate('page/all-icons')}
+      className={'flex w-full items-center rounded-lg px-4 py-2 hover:bg-surface-2'}
+    >
+      All Icons
     </button>
   );
 };

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

@@ -1,23 +1,17 @@
 import { useAppDispatch, useAppSelector } from '../../../stores/store';
 import { foldersActions } from '../../../stores/reducers/folders/slice';
 import { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc';
-import { useError } from '../../error/Error.hooks';
 
 export const useNewFolder = () => {
   const appDispatch = useAppDispatch();
   const workspace = useAppSelector((state) => state.workspace);
   const workspaceBackendService = new WorkspaceBackendService(workspace.id || '');
-  const error = useError();
 
   const onNewFolder = async () => {
-    try {
-      const newApp = await workspaceBackendService.createApp({
-        name: 'New Folder 1',
-      });
-      appDispatch(foldersActions.addFolder({ id: newApp.id, title: newApp.name }));
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    const newApp = await workspaceBackendService.createApp({
+      name: 'New Folder 1',
+    });
+    appDispatch(foldersActions.addFolder({ id: newApp.id, title: newApp.name }));
   };
 
   return {

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPagePopup.tsx

@@ -1,4 +1,4 @@
-import { IPopupItem, Popup } from '../../_shared/Popup';
+import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect';
 import { DocumentSvg } from '../../_shared/svg/DocumentSvg';
 import { BoardSvg } from '../../_shared/svg/BoardSvg';
 import { GridSvg } from '../../_shared/svg/GridSvg';
@@ -47,11 +47,11 @@ export const NewPagePopup = ({
   ];
 
   return (
-    <Popup
+    <PopupSelect
       onOutsideClick={() => onClose && onClose()}
       items={items}
       className={'absolute right-0'}
       style={{ top: `${top}px` }}
-    ></Popup>
+    ></PopupSelect>
   );
 };

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

@@ -13,7 +13,6 @@ export const usePageEvents = (page: IPage) => {
   const [activePageId, setActivePageId] = useState<string>('');
   const currentLocation = useLocation();
   const viewBackendService: ViewBackendService = new ViewBackendService(page.id);
-  const error = useError();
 
   useEffect(() => {
     const { pathname } = currentLocation;
@@ -32,33 +31,21 @@ export const usePageEvents = (page: IPage) => {
   };
 
   const changePageTitle = async (newTitle: string) => {
-    try {
-      await viewBackendService.update({ name: newTitle });
-      appDispatch(pagesActions.renamePage({ id: page.id, newTitle }));
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    await viewBackendService.update({ name: newTitle });
+    appDispatch(pagesActions.renamePage({ id: page.id, newTitle }));
   };
 
   const deletePage = async () => {
     closePopup();
-    try {
-      await viewBackendService.delete();
-      appDispatch(pagesActions.deletePage({ id: page.id }));
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    await viewBackendService.delete();
+    appDispatch(pagesActions.deletePage({ id: page.id }));
   };
 
   const duplicatePage = () => {
     closePopup();
-    try {
-      appDispatch(
-        pagesActions.addPage({ id: nanoid(8), pageType: page.pageType, title: page.title, folderId: page.folderId })
-      );
-    } catch (e: any) {
-      error.showError(e?.message);
-    }
+    appDispatch(
+      pagesActions.addPage({ id: nanoid(8), pageType: page.pageType, title: page.title, folderId: page.folderId })
+    );
   };
 
   const closePopup = () => {

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

@@ -3,13 +3,12 @@ import { useAppDispatch, useAppSelector } from '../../stores/store';
 import { pagesActions } from '../../stores/reducers/pages/slice';
 import { workspaceActions } from '../../stores/reducers/workspace/slice';
 import { UserBackendService } from '../../stores/effects/user/user_bd_svc';
-import { useError } from '../error/Error.hooks';
 
 export const useWorkspace = () => {
   const currentUser = useAppSelector((state) => state.currentUser);
 
   const appDispatch = useAppDispatch();
-  const error = useError();
+
   const userBackendService: UserBackendService = new UserBackendService(currentUser.id || 0);
 
   const loadWorkspaceItems = async () => {
@@ -31,15 +30,11 @@ export const useWorkspace = () => {
       }
     } catch (e1) {
       // create workspace for first start
-      try {
-        const workspace = await userBackendService.createWorkspace({ name: 'New Workspace', desc: '' });
-        appDispatch(workspaceActions.updateWorkspace({ id: workspace.id, name: workspace.name }));
-
-        appDispatch(foldersActions.clearFolders());
-        appDispatch(pagesActions.clearPages());
-      } catch (e2: any) {
-        error.showError(e2?.message);
-      }
+      const workspace = await userBackendService.createWorkspace({ name: 'New Workspace', desc: '' });
+      appDispatch(workspaceActions.updateWorkspace({ id: workspace.id, name: workspace.name }));
+
+      appDispatch(foldersActions.clearFolders());
+      appDispatch(pagesActions.clearPages());
     }
   };
 

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

@@ -0,0 +1,168 @@
+import AddSvg from '$app/components/_shared/svg/AddSvg';
+import { ArrowLeftSvg } from '$app/components/_shared/svg/ArrowLeftSvg';
+import { ArrowRightSvg } from '$app/components/_shared/svg/ArrowRightSvg';
+import { BoardSvg } from '$app/components/_shared/svg/BoardSvg';
+import { CheckboxSvg } from '$app/components/_shared/svg/CheckboxSvg';
+import { ChecklistTypeSvg } from '$app/components/_shared/svg/ChecklistTypeSvg';
+import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
+import { ClockSvg } from '$app/components/_shared/svg/ClockSvg';
+import { CloseSvg } from '$app/components/_shared/svg/CloseSvg';
+import { CopySvg } from '$app/components/_shared/svg/CopySvg';
+import { DateTypeSvg } from '$app/components/_shared/svg/DateTypeSvg';
+import { Details2Svg } from '$app/components/_shared/svg/Details2Svg';
+import { DocumentSvg } from '$app/components/_shared/svg/DocumentSvg';
+import { DropDownShowSvg } from '$app/components/_shared/svg/DropDownShowSvg';
+import { EarthSvg } from '$app/components/_shared/svg/EarthSvg';
+import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
+import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
+import { EditSvg } from '$app/components/_shared/svg/EditSvg';
+import { EyeClosedSvg } from '$app/components/_shared/svg/EyeClosedSvg';
+import { EyeOpenSvg } from '$app/components/_shared/svg/EyeOpenSvg';
+import { FilterSvg } from '$app/components/_shared/svg/FilterSvg';
+import { GridSvg } from '$app/components/_shared/svg/GridSvg';
+import { GroupByFieldSvg } from '$app/components/_shared/svg/GroupByFieldSvg';
+import { HideMenuSvg } from '$app/components/_shared/svg/HideMenuSvg';
+import { InformationSvg } from '$app/components/_shared/svg/InformationSvg';
+import { LogoutSvg } from '$app/components/_shared/svg/LogoutSvg';
+import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
+import { MultiSelectTypeSvg } from '$app/components/_shared/svg/MultiSelectTypeSvg';
+import { NumberTypeSvg } from '$app/components/_shared/svg/NumberTypeSvg';
+import { PropertiesSvg } from '$app/components/_shared/svg/PropertiesSvg';
+import { SearchSvg } from '$app/components/_shared/svg/SearchSvg';
+import { ShowMenuSvg } from '$app/components/_shared/svg/ShowMenuSvg';
+import { SingleSelectTypeSvg } from '$app/components/_shared/svg/SingleSelectTypeSvg';
+import { SkipLeftSvg } from '$app/components/_shared/svg/SkipLeftSvg';
+import { SkipRightSvg } from '$app/components/_shared/svg/SkipRightSvg';
+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';
+
+export const AllIcons = () => {
+  return (
+    <div className={'p-8'}>
+      <h1 className={'mb-12 text-2xl'}>Icons</h1>
+      <div className={'mb-8'}>
+        <div className={'flex flex-wrap items-center gap-8'}>
+          <i className={'h-5 w-5'} title={'AddSvg'}>
+            <AddSvg></AddSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'ArrowLeftSvg'}>
+            <ArrowLeftSvg></ArrowLeftSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'ArrowRightSvg'}>
+            <ArrowRightSvg></ArrowRightSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'BoardSvg'}>
+            <BoardSvg></BoardSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'CheckboxSvg'}>
+            <CheckboxSvg></CheckboxSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'ChecklistTypeSvg'}>
+            <ChecklistTypeSvg></ChecklistTypeSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'CheckmarkSvg'}>
+            <CheckmarkSvg></CheckmarkSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'ClockSvg'}>
+            <ClockSvg></ClockSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'CloseSvg'}>
+            <CloseSvg></CloseSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'CopySvg'}>
+            <CopySvg></CopySvg>
+          </i>
+          <i className={'h-5 w-5'} title={'DateTypeSvg'}>
+            <DateTypeSvg></DateTypeSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'Details2Svg'}>
+            <Details2Svg></Details2Svg>
+          </i>
+          <i className={'h-5 w-5'} title={'DocumentSvg'}>
+            <DocumentSvg></DocumentSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'DropDownShowSvg'}>
+            <DropDownShowSvg></DropDownShowSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'EarthSvg'}>
+            <EarthSvg></EarthSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'EditorCheckSvg'}>
+            <EditorCheckSvg></EditorCheckSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'EditorUncheckSvg'}>
+            <EditorUncheckSvg></EditorUncheckSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'EditSvg'}>
+            <EditSvg></EditSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'EyeClosedSvg'}>
+            <EyeClosedSvg></EyeClosedSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'EyeOpenSvg'}>
+            <EyeOpenSvg></EyeOpenSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'FilterSvg'}>
+            <FilterSvg></FilterSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'GridSvg'}>
+            <GridSvg></GridSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'GroupByFieldSvg'}>
+            <GroupByFieldSvg></GroupByFieldSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'HideMenuSvg'}>
+            <HideMenuSvg></HideMenuSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'InformationSvg'}>
+            <InformationSvg></InformationSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'LogoutSvg'}>
+            <LogoutSvg></LogoutSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'MoreSvg'}>
+            <MoreSvg></MoreSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'MultiSelectTypeSvg'}>
+            <MultiSelectTypeSvg></MultiSelectTypeSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'NumberTypeSvg'}>
+            <NumberTypeSvg></NumberTypeSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'PropertiesSvg'}>
+            <PropertiesSvg></PropertiesSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'SearchSvg'}>
+            <SearchSvg></SearchSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'ShowMenuSvg'}>
+            <ShowMenuSvg></ShowMenuSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'SingleSelectTypeSvg'}>
+            <SingleSelectTypeSvg></SingleSelectTypeSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'SkipLeftSvg'}>
+            <SkipLeftSvg></SkipLeftSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'SkipRightSvg'}>
+            <SkipRightSvg></SkipRightSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'SortSvg'}>
+            <SortSvg></SortSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'TextTypeSvg'}>
+            <TextTypeSvg></TextTypeSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'TrashSvg'}>
+            <TrashSvg></TrashSvg>
+          </i>
+          <i className={'h-5 w-5'} title={'UrlTypeSvg'}>
+            <UrlTypeSvg></UrlTypeSvg>
+          </i>
+        </div>
+      </div>
+    </div>
+  );
+};

+ 45 - 0
frontend/appflowy_tauri/src/appflowy_app/components/tests/ColorPalette.tsx

@@ -0,0 +1,45 @@
+export const ColorPalette = () => {
+  return (
+    <div className={'p-8'}>
+      <h1 className={'mb-4 text-2xl'}>Colors</h1>
+      <h2 className={'mb-4'}>Main</h2>
+      <div className={'mb-8 flex flex-wrap items-center'}>
+        <div title={'main-accent'} className={'m-2 h-[100px] w-[100px] bg-main-accent'}></div>
+        <div title={'main-hovered'} className={'m-2 h-[100px] w-[100px] bg-main-hovered'}></div>
+        <div title={'main-secondary'} className={'m-2 h-[100px] w-[100px] bg-main-secondary'}></div>
+        <div title={'main-selector'} className={'m-2 h-[100px] w-[100px] bg-main-selector'}></div>
+        <div title={'main-alert'} className={'m-2 h-[100px] w-[100px] bg-main-alert'}></div>
+        <div title={'main-warning'} className={'m-2 h-[100px] w-[100px] bg-main-warning'}></div>
+        <div title={'main-success'} className={'m-2 h-[100px] w-[100px] bg-main-success'}></div>
+      </div>
+      <h2 className={'mb-4'}>Tint</h2>
+      <div className={'mb-8 flex flex-wrap items-center'}>
+        <div title={'tint-1'} className={'m-2 h-[100px] w-[100px] bg-tint-1'}></div>
+        <div title={'tint-2'} className={'m-2 h-[100px] w-[100px] bg-tint-2'}></div>
+        <div title={'tint-3'} className={'m-2 h-[100px] w-[100px] bg-tint-3'}></div>
+        <div title={'tint-4'} className={'m-2 h-[100px] w-[100px] bg-tint-4'}></div>
+        <div title={'tint-5'} className={'m-2 h-[100px] w-[100px] bg-tint-5'}></div>
+        <div title={'tint-6'} className={'m-2 h-[100px] w-[100px] bg-tint-6'}></div>
+        <div title={'tint-7'} className={'m-2 h-[100px] w-[100px] bg-tint-7'}></div>
+        <div title={'tint-8'} className={'m-2 h-[100px] w-[100px] bg-tint-8'}></div>
+        <div title={'tint-9'} className={'m-2 h-[100px] w-[100px] bg-tint-9'}></div>
+      </div>
+      <h2 className={'mb-4'}>Shades</h2>
+      <div className={'mb-8 flex flex-wrap items-center'}>
+        <div title={'shade-1'} className={'m-2 h-[100px] w-[100px] bg-shade-1'}></div>
+        <div title={'shade-2'} className={'m-2 h-[100px] w-[100px] bg-shade-2'}></div>
+        <div title={'shade-3'} className={'m-2 h-[100px] w-[100px] bg-shade-3'}></div>
+        <div title={'shade-4'} className={'m-2 h-[100px] w-[100px] bg-shade-4'}></div>
+        <div title={'shade-5'} className={'m-2 h-[100px] w-[100px] bg-shade-5'}></div>
+        <div title={'shade-6'} className={'m-2 h-[100px] w-[100px] bg-shade-6'}></div>
+      </div>
+      <h2 className={'mb-4'}>Surface</h2>
+      <div className={'mb-8 flex flex-wrap items-center'}>
+        <div title={'surface-1'} className={'m-2 h-[100px] w-[100px] bg-surface-1'}></div>
+        <div title={'surface-2'} className={'m-2 h-[100px] w-[100px] bg-surface-2'}></div>
+        <div title={'surface-3'} className={'m-2 h-[100px] w-[100px] bg-surface-3'}></div>
+        <div title={'surface-4'} className={'bg-surface-4 m-2 h-[100px] w-[100px]'}></div>
+      </div>
+    </div>
+  );
+};

+ 0 - 0
frontend/appflowy_tauri/src/appflowy_app/components/TestFonts/TestFonts.tsx → frontend/appflowy_tauri/src/appflowy_app/components/tests/TestFonts.tsx


+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_controller.ts

@@ -65,7 +65,7 @@ export class DatabaseController {
       this.databaseViewCache.initializeWithRows(database.rows);
 
       this._callback?.onViewChanged?.(database);
-      return loadGroupResult;
+      return Ok(database.rows);
     } else {
       return Err(openDatabaseResult.val);
     }

+ 6 - 4
frontend/appflowy_tauri/src/appflowy_app/views/BoardPage.tsx

@@ -1,22 +1,24 @@
 import { useParams } from 'react-router-dom';
 import { useEffect, useState } from 'react';
 import { Board } from '../components/board/Board';
+import { useAppSelector } from '$app/stores/store';
 
 export const BoardPage = () => {
   const params = useParams();
   const [viewId, setViewId] = useState('');
+  const pagesStore = useAppSelector((state) => state.pages);
+  const [title, setTitle] = useState('');
 
   useEffect(() => {
     if (params?.id?.length) {
       setViewId(params.id);
-      // setDatabaseId('testDb');
+      setTitle(pagesStore.find((page) => page.id === params.id)?.title || '');
     }
-  }, [params]);
+  }, [params, pagesStore]);
 
   return (
     <div className='flex h-full flex-col gap-8 px-8 pt-8'>
-      <h1 className='text-4xl font-bold'>Board: {viewId}</h1>
-      {viewId?.length && <Board viewId={viewId} />}
+      {viewId?.length && <Board viewId={viewId} title={title} />}
     </div>
   );
 };

+ 0 - 9
frontend/appflowy_tauri/src/appflowy_app/views/GridPage.hooks.ts

@@ -1,9 +0,0 @@
-export const useGrid = () => {
-  const loadGrid = async (id: string) => {
-    console.log('loading grid');
-  };
-
-  return {
-    loadGrid,
-  };
-};

+ 11 - 34
frontend/appflowy_tauri/src/appflowy_app/views/GridPage.tsx

@@ -1,45 +1,22 @@
-import { GridAddView } from '../components/grid/GridAddView/GridAddView';
-import { GridTableCount } from '../components/grid/GridTableCount/GridTableCount';
-import { GridTableHeader } from '../components/grid/GridTableHeader/GridTableHeader';
-import { GridAddRow } from '../components/grid/GridTableRows/GridAddRow';
-import { GridTableRows } from '../components/grid/GridTableRows/GridTableRows';
-import { GridTitle } from '../components/grid/GridTitle/GridTitle';
-import { SearchInput } from '../components/_shared/SearchInput';
-import { GridToolbar } from '../components/grid/GridToolbar/GridToolbar';
 import { useParams } from 'react-router-dom';
-import { useGrid } from './GridPage.hooks';
-import { useEffect } from 'react';
+
+import { useEffect, useState } from 'react';
+import { Grid } from '../components/grid/Grid/Grid';
 
 export const GridPage = () => {
   const params = useParams();
-  const { loadGrid } = useGrid();
+  const [viewId, setViewId] = useState('');
   useEffect(() => {
-    void (async () => {
-      if (!params?.id) return;
-      await loadGrid(params.id);
-    })();
+    if (params?.id?.length) {
+      setViewId(params.id);
+      // setDatabaseId('testDb');
+    }
   }, [params]);
 
   return (
-    <div className='mx-auto mt-8 flex flex-col gap-8 px-8'>
-      <h1 className='text-4xl font-bold'>Grid</h1>
-
-      <div className='flex w-full  items-center justify-between'>
-        <GridTitle />
-        <GridToolbar />
-      </div>
-
-      {/* table component view with text area for td */}
-      <div className='flex flex-col gap-4'>
-        <table className='w-full table-fixed text-sm'>
-          <GridTableHeader />
-          <GridTableRows />
-        </table>
-
-        <GridAddRow />
-      </div>
-
-      <GridTableCount />
+    <div className='flex h-full flex-col gap-8 px-8 pt-8'>
+      <h1 className='text-4xl font-bold'>Grid: {viewId}</h1>
+      {viewId?.length && <Grid viewId={viewId} />}
     </div>
   );
 };

Some files were not shown because too many files changed in this diff