mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: cell options layout
This commit is contained in:
parent
5762419aae
commit
5825195b8e
@ -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>
|
||||
))}
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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'}>
|
||||
|
@ -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,37 +29,24 @@ 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 onEditFieldClick = (cellIdentifier: CellIdentifier, top: number, right: number) => {
|
||||
setEditingCell(cellIdentifier);
|
||||
setEditFieldTop(top);
|
||||
setEditFieldRight(right);
|
||||
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(() => {
|
||||
@ -65,7 +54,26 @@ export const EditRow = ({
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const changeFieldTypeClick = async (newType: FieldType) => {
|
||||
const onEditFieldClick = (cellIdentifier: CellIdentifier, top: number, right: number) => {
|
||||
setEditingCell(cellIdentifier);
|
||||
setEditFieldTop(top);
|
||||
setEditFieldRight(right);
|
||||
setShowFieldEditor(true);
|
||||
};
|
||||
|
||||
const onOutsideEditFieldClick = () => {
|
||||
if (!showChangeFieldTypePopup) {
|
||||
setShowFieldEditor(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeFieldTypeClick = (buttonTop: number, buttonRight: number) => {
|
||||
setChangeFieldTypeTop(buttonTop);
|
||||
setChangeFieldTypeRight(buttonRight);
|
||||
setShowChangeFieldTypePopup(true);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user