ソースを参照

chore: cell options layout

ascarbek 2 年 前
コミット
5825195b8e

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

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

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

@@ -0,0 +1,99 @@
+import { useEffect, useRef, useState } from 'react';
+import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
+import { useCell } from '$app/components/_shared/database-hooks/useCell';
+import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
+import { FieldController } from '$app/stores/effects/database/field/field_controller';
+import { SelectOptionCellDataPB } from '@/services/backend';
+import { getBgColor } from '$app/components/_shared/getColor';
+import { useTranslation } from 'react-i18next';
+import { Details2Svg } from '$app/components/_shared/svg/Details2Svg';
+import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
+import { CloseSvg } from '$app/components/_shared/svg/CloseSvg';
+
+export const CellOptionsPopup = ({
+  top,
+  left,
+  cellIdentifier,
+  cellCache,
+  fieldController,
+}: {
+  top: number;
+  left: number;
+  cellIdentifier: CellIdentifier;
+  cellCache: CellCache;
+  fieldController: FieldController;
+}) => {
+  const ref = useRef<HTMLDivElement>(null);
+  const { t } = useTranslation('');
+  const [adjustedTop, setAdjustedTop] = useState(-100);
+  const [value, setValue] = useState('');
+  const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
+
+  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, left]);
+
+  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 + 50}px`, left: `${left}px` }}
+    >
+      <div 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) => (
+              <div className={`${getBgColor(option.color)} flex items-center gap-0.5 rounded px-1 py-0.5`} key={index}>
+                <span>{option?.name || ''}</span>
+                <i className={'h-5 w-5 cursor-pointer'}>
+                  <CloseSvg></CloseSvg>{' '}
+                </i>
+              </div>
+            )) || ''}
+          </div>
+          <input
+            className={'py-2'}
+            value={value}
+            onChange={(e) => setValue(e.target.value)}
+            placeholder={t('grid.selectOption.searchOption') || ''}
+          />
+          <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={'flex flex-col gap-1'}>
+          {(data as SelectOptionCellDataPB | undefined)?.options.map((option, index) => (
+            <div
+              key={index}
+              className={
+                'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'
+              }
+            >
+              <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`}>{option.name}</div>
+              <div className={'flex items-center'}>
+                {(data as SelectOptionCellDataPB | undefined)?.select_options?.find(
+                  (selectedOption) => selectedOption.id === option.id
+                ) && (
+                  <button className={'h-5 w-5 p-1'}>
+                    <CheckmarkSvg></CheckmarkSvg>
+                  </button>
+                )}
+                <button className={'h-6 w-6 p-1'}>
+                  <Details2Svg></Details2Svg>
+                </button>
+              </div>
+            </div>
+          ))}
+        </div>
+      </div>
+    </div>
+  );
+};

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

@@ -11,17 +11,20 @@ import { EditCellText } from '$app/components/_shared/EditRow/EditCellText';
 import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
 import { EditCellDate } from '$app/components/_shared/EditRow/EditCellDate';
 import { useRef } from 'react';
+import { CellOptions } from '$app/components/_shared/EditRow/CellOptions';
 
 export const EditCellWrapper = ({
   cellIdentifier,
   cellCache,
   fieldController,
   onEditFieldClick,
+  onEditOptionsClick,
 }: {
   cellIdentifier: CellIdentifier;
   cellCache: CellCache;
   fieldController: FieldController;
   onEditFieldClick: (top: number, right: number) => void;
+  onEditOptionsClick: (left: number, top: number) => void;
 }) => {
   const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
   const databaseStore = useAppSelector((state) => state.database);
@@ -50,15 +53,13 @@ export const EditCellWrapper = ({
       <div className={'flex-1 cursor-pointer rounded-lg px-4 py-2 hover:bg-shade-6'}>
         {(cellIdentifier.fieldType === FieldType.SingleSelect ||
           cellIdentifier.fieldType === FieldType.MultiSelect ||
-          cellIdentifier.fieldType === FieldType.Checklist) && (
-          <div className={'flex items-center gap-2'}>
-            {(data as SelectOptionCellDataPB | undefined)?.select_options?.map((option, index) => (
-              <div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
-                {option?.name || ''}
-              </div>
-            )) || ''}
-          </div>
-        )}
+          cellIdentifier.fieldType === FieldType.Checklist) &&
+          cellController && (
+            <CellOptions
+              data={data as SelectOptionCellDataPB | undefined}
+              onEditClick={onEditOptionsClick}
+            ></CellOptions>
+          )}
 
         {cellIdentifier.fieldType === FieldType.Checkbox && (
           <div className={'h-8 w-8'}>

+ 51 - 25
frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/EditRow.tsx

@@ -12,6 +12,8 @@ import { ChangeFieldTypePopup } from '$app/components/_shared/EditRow/ChangeFiel
 import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
 import { Some } from 'ts-results';
 import { FieldType } from '@/services/backend';
+import { CellOptions } from '$app/components/_shared/EditRow/CellOptions';
+import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPopup';
 
 export const EditRow = ({
   onClose,
@@ -27,18 +29,31 @@ export const EditRow = ({
   const { cells, onNewColumnClick } = useRow(viewId, controller, rowInfo);
   const { t } = useTranslation('');
   const [unveil, setUnveil] = useState(false);
+
+  const [editingCell, setEditingCell] = useState<CellIdentifier | null>(null);
   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 [editingCell, setEditingCell] = useState<CellIdentifier | null>(null);
+
+  const [showChangeOptionsPopup, setShowChangeOptionsPopup] = useState(false);
+  const [changeOptionsTop, setChangeOptionsTop] = useState(0);
+  const [changeOptionsLeft, setChangeOptionsLeft] = useState(0);
 
   useEffect(() => {
     setUnveil(true);
   }, []);
 
+  const onCloseClick = () => {
+    setUnveil(false);
+    setTimeout(() => {
+      onClose();
+    }, 300);
+  };
+
   const onEditFieldClick = (cellIdentifier: CellIdentifier, top: number, right: number) => {
     setEditingCell(cellIdentifier);
     setEditFieldTop(top);
@@ -46,26 +61,19 @@ export const EditRow = ({
     setShowFieldEditor(true);
   };
 
-  const onChangeFieldTypeClick = (buttonTop: number, buttonRight: number) => {
-    setChangeFieldTypeTop(buttonTop);
-    setChangeFieldTypeRight(buttonRight);
-    setShowChangeFieldTypePopup(true);
-  };
-
   const onOutsideEditFieldClick = () => {
     if (!showChangeFieldTypePopup) {
       setShowFieldEditor(false);
     }
   };
 
-  const onCloseClick = () => {
-    setUnveil(false);
-    setTimeout(() => {
-      onClose();
-    }, 300);
+  const onChangeFieldTypeClick = (buttonTop: number, buttonRight: number) => {
+    setChangeFieldTypeTop(buttonTop);
+    setChangeFieldTypeRight(buttonRight);
+    setShowChangeFieldTypePopup(true);
   };
 
-  const changeFieldTypeClick = async (newType: FieldType) => {
+  const changeFieldType = async (newType: FieldType) => {
     if (!editingCell) return;
 
     const currentField = controller.fieldController.getField(editingCell.fieldId);
@@ -79,6 +87,13 @@ export const EditRow = ({
     setShowChangeFieldTypePopup(false);
   };
 
+  const onEditOptionsClick = async (cellIdentifier: CellIdentifier, left: number, top: number) => {
+    setEditingCell(cellIdentifier);
+    setChangeOptionsLeft(left);
+    setChangeOptionsTop(top);
+    setShowChangeOptionsPopup(true);
+  };
+
   return (
     <div
       className={`fixed inset-0 z-10 flex items-center justify-center bg-black/30 backdrop-blur-sm transition-opacity duration-300 ${
@@ -99,9 +114,22 @@ export const EditRow = ({
               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)}
             ></EditCellWrapper>
           ))}
         </div>
+        <div className={'border-t border-shade-6 pt-2'}>
+          <button
+            onClick={() => onNewColumnClick()}
+            className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-shade-6'}
+          >
+            <i className={'h-5 w-5'}>
+              <AddSvg></AddSvg>
+            </i>
+            <span>{t('grid.field.newColumn')}</span>
+          </button>
+        </div>
+
         {showFieldEditor && editingCell && (
           <EditFieldPopup
             top={editFieldTop}
@@ -117,21 +145,19 @@ export const EditRow = ({
           <ChangeFieldTypePopup
             top={changeFieldTypeTop}
             right={changeFieldTypeRight}
-            onClick={(newType) => changeFieldTypeClick(newType)}
+            onClick={(newType) => changeFieldType(newType)}
             onOutsideClick={() => setShowChangeFieldTypePopup(false)}
           ></ChangeFieldTypePopup>
         )}
-        <div className={'border-t border-shade-6 pt-2'}>
-          <button
-            onClick={() => onNewColumnClick()}
-            className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-shade-6'}
-          >
-            <i className={'h-5 w-5'}>
-              <AddSvg></AddSvg>
-            </i>
-            <span>{t('grid.field.newColumn')}</span>
-          </button>
-        </div>
+        {showChangeOptionsPopup && editingCell && (
+          <CellOptionsPopup
+            top={changeOptionsTop}
+            left={changeOptionsLeft}
+            cellIdentifier={editingCell}
+            cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
+            fieldController={controller.fieldController}
+          ></CellOptionsPopup>
+        )}
       </div>
     </div>
   );

+ 7 - 0
frontend/appflowy_tauri/src/appflowy_app/components/_shared/svg/CheckmarkSvg.tsx

@@ -0,0 +1,7 @@
+export const CheckmarkSvg = () => {
+  return (
+    <svg width='100%' height='100%' viewBox='0 0 10 8' fill='none' xmlns='http://www.w3.org/2000/svg'>
+      <path d='M1 5.2L2.84615 7L9 1' stroke='#00BCF0' strokeLinecap='round' strokeLinejoin='round' />
+    </svg>
+  );
+};