1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-31 14:42:10 +00:00

optimize add column interaction (#7546)

* fix icon props error

* optimize add column interaction

---------

Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
Aries
2025-03-04 12:47:13 +08:00
committed by GitHub
parent 6cb0522a4c
commit cc0d3a281c
6 changed files with 344 additions and 289 deletions

View File

@@ -0,0 +1,252 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle, Input } from 'reactstrap';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import Icon from '../../../../components/icon';
import { gettext } from '../../../../utils/constants';
import { CellType, COLUMNS_ICON_CONFIG, DEFAULT_DATE_FORMAT, DEFAULT_RATE_DATA, DEFAULT_SHOOTING_TIME_FORMAT, PRIVATE_COLUMN_KEY } from '../../../constants';
import { getColumnDisplayName } from '../../../utils/column';
const COLUMNS = [
{
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
type: CellType.COLLABORATOR,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_COLLABORATORS),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_COLLABORATORS,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
type: CellType.COLLABORATOR,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_REVIEWER),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_REVIEWER,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
type: CellType.COLLABORATOR,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.OWNER),
unique: true,
key: PRIVATE_COLUMN_KEY.OWNER,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
type: CellType.DATE,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME,
canChangeName: false,
data: { format: DEFAULT_DATE_FORMAT },
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT],
type: CellType.LONG_TEXT,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_DESCRIPTION),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_DESCRIPTION,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT],
type: CellType.SINGLE_SELECT,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_STATUS),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_STATUS,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
type: CellType.DATE,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.CAPTURE_TIME),
unique: true,
key: PRIVATE_COLUMN_KEY.CAPTURE_TIME,
canChangeName: false,
data: { format: DEFAULT_SHOOTING_TIME_FORMAT },
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.RATE],
type: CellType.RATE,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_RATE),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_RATE,
canChangeName: false,
data: DEFAULT_RATE_DATA,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.TEXT],
type: CellType.TEXT,
name: gettext('Text'),
canChangeName: true,
key: CellType.TEXT,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT],
type: CellType.LONG_TEXT,
name: gettext('Long text'),
canChangeName: true,
key: CellType.LONG_TEXT,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.NUMBER],
type: CellType.NUMBER,
name: gettext('Number'),
canChangeName: true,
key: CellType.NUMBER,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
type: CellType.COLLABORATOR,
name: gettext('Collaborator'),
canChangeName: true,
key: CellType.COLLABORATOR,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.CHECKBOX],
type: CellType.CHECKBOX,
name: gettext('Checkbox'),
canChangeName: true,
key: CellType.CHECKBOX,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
type: CellType.DATE,
name: gettext('Date'),
canChangeName: true,
key: CellType.DATE,
data: { format: DEFAULT_DATE_FORMAT },
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT],
type: CellType.SINGLE_SELECT,
name: gettext('Single select'),
canChangeName: true,
key: CellType.SINGLE_SELECT,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.MULTIPLE_SELECT],
type: CellType.MULTIPLE_SELECT,
name: gettext('Multiple select'),
canChangeName: true,
key: CellType.MULTIPLE_SELECT,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.RATE],
type: CellType.RATE,
name: gettext('Rate'),
canChangeName: true,
key: CellType.RATE,
data: DEFAULT_RATE_DATA,
groupby: 'basics',
},
];
const CustomDropdownMenu = ({ column, modifiers, onSelect }) => {
const [searchValue, setSearchValue] = useState('');
const [isCustomPropertiesOpen, setCustomPropertiesOpen] = useState(false);
const inputRef = useRef(null);
const displayColumns = useMemo(() => {
const validValue = searchValue.trim().toLocaleLowerCase();
return COLUMNS.filter(item => {
const columnName = item.name.toLocaleLowerCase();
return columnName.indexOf(validValue) > -1;
});
}, [searchValue]);
const basicsColumns = useMemo(() => {
return displayColumns.filter(item => item.groupby === 'basics');
}, [displayColumns]);
const predefinedColumns = useMemo(() => {
return displayColumns.filter(item => item.groupby === 'predefined');
}, [displayColumns]);
const toggleCustomProperties = useCallback((e) => {
e?.stopPropagation();
setCustomPropertiesOpen(prev => !prev);
setSearchValue('');
}, []);
const onSearchColumn = useCallback((event) => {
const value = event.target.value;
if (value === searchValue) return;
setSearchValue(value);
}, [searchValue]);
const onSearchClick = useCallback((event) => {
event.stopPropagation();
}, []);
const handleSelect = useCallback((column) => {
onSelect(column);
setSearchValue('');
}, [onSelect]);
return (
<DropdownMenu modifiers={modifiers}>
<div className="search-column-container">
<Input onChange={onSearchColumn} placeholder={gettext('Search properties')} value={searchValue} onClick={onSearchClick} ref={inputRef} />
</div>
{displayColumns.length > 0 && predefinedColumns.length > 0 && (
<>
{predefinedColumns.map(item => (
<DropdownItem
key={item.key}
className={classnames('column-type-item text-truncate', { 'active': item.key === column?.key })}
onMouseEnter={() => setCustomPropertiesOpen(false)}
onClick={() => handleSelect(item)}
>
<Icon symbol={item.icon} className="sf-metadata-icon" />
<span>{item.name}</span>
</DropdownItem>
))}
{basicsColumns.length > 0 && (
<>
<DropdownItem className="w-100" divider />
<Dropdown
className="w-100"
direction="end"
isOpen={isCustomPropertiesOpen}
toggle={toggleCustomProperties}
onMouseEnter={() => setCustomPropertiesOpen(true)}
onMouseMove={(e) => {e.stopPropagation();}}
>
<DropdownToggle
tag='span'
className="column-type-item dropdown-item text-truncate"
>
<Icon symbol="edit" className="sf-metadata-icon" />
<span className="mr-auto">{gettext('Custom properties')}</span>
<i className="sf3-font-down sf3-font rotate-270"></i>
</DropdownToggle>
<DropdownMenu>
{basicsColumns.map((item, index) => (
<DropdownItem
key={index}
className={classnames('column-type-item text-truncate', { 'active': item.key === column?.key })}
onClick={() => handleSelect(item)}
>
<Icon symbol={item.icon} className="sf-metadata-icon" />
<span>{item.name}</span>
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
</>
)}
</>
)}
</DropdownMenu>
);
};
CustomDropdownMenu.propTypes = {
column: PropTypes.object,
modifiers: PropTypes.array,
onSelect: PropTypes.func,
};
export default CustomDropdownMenu;

View File

@@ -12,13 +12,11 @@ const Data = forwardRef(({ column }, ref) => {
const [value, setValue] = useState(column.data || {});
const [popoverShow, setPopoverShow] = useState(false);
const columnKey = useRef(null);
// const [error, setError] = useState('');
useImperativeHandle(ref, () => ({
getValue: () => value,
setValue: (value) => setValue(value),
getIsPopoverShow: () => popoverShow,
// setError: (error) => setError(error),
}), [popoverShow, value]);
const onChange = useCallback((value) => {

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import React, { useCallback, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { Button, UncontrolledPopover } from 'reactstrap';
import { Button, Popover } from 'reactstrap';
import classnames from 'classnames';
import { gettext } from '../../../../utils/constants';
import { useMetadataView } from '../../../hooks/metadata-view';
@@ -18,9 +18,7 @@ import './index.css';
const DEFAULT_POPOVER_INNER_WIDTH = 350;
const COLUMN_TYPE_POPOVER_INNER_WIDTH = {};
const ColumnPopover = ({ target, onChange }) => {
const [column, setColumn] = useState({});
const popoverRef = useRef(null);
const ColumnPopover = ({ target, column, onSelect, onCancel, onSubmit }) => {
const popoverInnerRef = useRef(null);
const nameRef = useRef(null);
const typeRef = useRef(null);
@@ -31,23 +29,17 @@ const ColumnPopover = ({ target, onChange }) => {
return COLUMN_TYPE_POPOVER_INNER_WIDTH[column.type] || DEFAULT_POPOVER_INNER_WIDTH;
}, [column]);
const toggle = useCallback((event) => {
if (typeRef.current?.getIsPopoverShow()) return;
if (dataRef.current?.getIsPopoverShow()) return;
popoverRef.current.toggle();
}, [typeRef, dataRef]);
const onColumnChange = useCallback((newColumn) => {
setTimeout(() => {
typeRef.current.setPopoverState(false);
}, 100);
if (ObjectUtils.isSameObject(column, newColumn)) return;
setColumn(newColumn);
onSelect(newColumn);
if (newColumn.type === column.type) return;
dataRef.current.setValue({});
}, [typeRef, column]);
}, [typeRef, column, onSelect]);
const onSubmit = useCallback(() => {
const handleSubmit = useCallback(() => {
nameRef.current.setError('');
typeRef.current.setError('');
let flag = 1;
@@ -80,19 +72,16 @@ const ColumnPopover = ({ target, onChange }) => {
}
}
}
onChange(column.unique ? column.key : columnName, column.type, { key: column.unique ? column.key : '', data });
toggle();
}, [nameRef, column, metadata, onChange, toggle]);
onSubmit(column.unique ? column.key : columnName, column.type, { key: column.unique ? column.key : '', data });
}, [nameRef, column, metadata, onSubmit]);
return (
<UncontrolledPopover
<Popover
target={target}
trigger="legacy"
isOpen={true}
placement="bottom-end"
hideArrow={true}
toggle={toggle}
fade={false}
ref={popoverRef}
className="sf-metadata-column-popover"
>
<div className="sf-metadata-column-popover-inner" ref={popoverInnerRef} style={{ width: popoverInnerWidth }}>
@@ -102,17 +91,20 @@ const ColumnPopover = ({ target, onChange }) => {
<Data ref={dataRef} column={column} />
</div>
<div className={classnames('sf-metadata-column-popover-footer', { 'sf-metadata-number-column-popover-footer': column.type === CellType.NUMBER })}>
<Button color="secondary" className="mr-4" onClick={toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={onSubmit}>{gettext('Submit')}</Button>
<Button color="secondary" className="mr-4" onClick={onCancel}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={handleSubmit}>{gettext('Submit')}</Button>
</div>
</div>
</UncontrolledPopover>
</Popover>
);
};
ColumnPopover.propTypes = {
target: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
column: PropTypes.object.isRequired,
onSelect: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
};
export default ColumnPopover;

View File

@@ -22,6 +22,7 @@ const Name = forwardRef(({ readOnly, value }, ref) => {
useEffect(() => {
setName(value);
setError('');
}, [value]);
return (

View File

@@ -1,204 +1,24 @@
import React, { forwardRef, useImperativeHandle, useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { FormGroup, FormFeedback, Label, Dropdown, DropdownToggle, DropdownMenu, Input, DropdownItem } from 'reactstrap';
import React, { forwardRef, useImperativeHandle, useState, useCallback, useRef, useEffect } from 'react';
import { FormGroup, FormFeedback, Label, Dropdown, DropdownToggle } from 'reactstrap';
import Icon from '@/components/icon';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { gettext } from '../../../../../utils/constants';
import { getColumnDisplayName } from '../../../../utils/column';
import { CellType, COLUMNS_ICON_CONFIG, DEFAULT_DATE_FORMAT, DEFAULT_SHOOTING_TIME_FORMAT, PRIVATE_COLUMN_KEY, DEFAULT_RATE_DATA } from '../../../../constants';
import CustomDropdownMenu from '../custom-dropdown-menu';
import './index.css';
const COLUMNS = [
{
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
type: CellType.COLLABORATOR,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_COLLABORATORS),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_COLLABORATORS,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
type: CellType.COLLABORATOR,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_REVIEWER),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_REVIEWER,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
type: CellType.COLLABORATOR,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.OWNER),
unique: true,
key: PRIVATE_COLUMN_KEY.OWNER,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
type: CellType.DATE,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_EXPIRE_TIME,
canChangeName: false,
data: { format: DEFAULT_DATE_FORMAT },
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT],
type: CellType.LONG_TEXT,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_DESCRIPTION),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_DESCRIPTION,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT],
type: CellType.SINGLE_SELECT,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_STATUS),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_STATUS,
canChangeName: false,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
type: CellType.DATE,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.CAPTURE_TIME),
unique: true,
key: PRIVATE_COLUMN_KEY.CAPTURE_TIME,
canChangeName: false,
data: { format: DEFAULT_SHOOTING_TIME_FORMAT },
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.RATE],
type: CellType.RATE,
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.FILE_RATE),
unique: true,
key: PRIVATE_COLUMN_KEY.FILE_RATE,
canChangeName: false,
data: DEFAULT_RATE_DATA,
groupby: 'predefined'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.TEXT],
type: CellType.TEXT,
name: gettext('Text'),
canChangeName: true,
key: CellType.TEXT,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.LONG_TEXT],
type: CellType.LONG_TEXT,
name: gettext('Long text'),
canChangeName: true,
key: CellType.LONG_TEXT,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.NUMBER],
type: CellType.NUMBER,
name: gettext('Number'),
canChangeName: true,
key: CellType.NUMBER,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.COLLABORATOR],
type: CellType.COLLABORATOR,
name: gettext('Collaborator'),
canChangeName: true,
key: CellType.COLLABORATOR,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.CHECKBOX],
type: CellType.CHECKBOX,
name: gettext('Checkbox'),
canChangeName: true,
key: CellType.CHECKBOX,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.DATE],
type: CellType.DATE,
name: gettext('Date'),
canChangeName: true,
key: CellType.DATE,
data: { format: DEFAULT_DATE_FORMAT },
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.SINGLE_SELECT],
type: CellType.SINGLE_SELECT,
name: gettext('Single select'),
canChangeName: true,
key: CellType.SINGLE_SELECT,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.MULTIPLE_SELECT],
type: CellType.MULTIPLE_SELECT,
name: gettext('Multiple select'),
canChangeName: true,
key: CellType.MULTIPLE_SELECT,
groupby: 'basics'
}, {
icon: COLUMNS_ICON_CONFIG[CellType.RATE],
type: CellType.RATE,
name: gettext('Rate'),
canChangeName: true,
key: CellType.RATE,
data: DEFAULT_RATE_DATA,
groupby: 'basics',
},
];
const Type = forwardRef(({ column, onChange }, ref) => {
const [error, setError] = useState('');
const [searchValue, setSearchValue] = useState('');
const [isPredefinedPropertiesOpen, setPredefinedPropertiesOpen] = useState(false);
const [isCustomPropertiesOpen, setCustomPropertiesOpen] = useState(false);
const inputRef = useRef(null);
const displayColumns = useMemo(() => {
const validValue = searchValue.trim().toLocaleLowerCase();
return COLUMNS.filter(item => {
const columnName = item.name.toLocaleLowerCase();
return columnName.indexOf(validValue) > -1;
});
}, [searchValue]);
const basicsColumns = useMemo(() => {
return displayColumns.filter(item => item.groupby === 'basics');
}, [displayColumns]);
const predefinedColumns = useMemo(() => {
return displayColumns.filter(item => item.groupby === 'predefined');
}, [displayColumns]);
const togglePredefinedProperties = useCallback((e) => {
e?.stopPropagation();
setPredefinedPropertiesOpen(prev => !prev);
}, []);
const toggleCustomProperties = useCallback((e) => {
e?.stopPropagation();
setCustomPropertiesOpen(prev => !prev);
setSearchValue('');
}, []);
const onSelectPredefinedColumn = useCallback((column) => {
onChange(column);
setSearchValue('');
}, [onChange]);
const onSelectCustomColumn = useCallback((column) => {
onChange(column);
togglePredefinedProperties();
}, [onChange, togglePredefinedProperties]);
const onSearchColumn = useCallback((event) => {
const value = event.target.value;
if (value === searchValue) return;
setSearchValue(value);
}, [searchValue]);
const onSearchClick = useCallback((event) => {
event.stopPropagation();
}, []);
useImperativeHandle(ref, () => ({
setError: (error) => setError(error),
getIsPopoverShow: () => isPredefinedPropertiesOpen || isCustomPropertiesOpen,
@@ -213,8 +33,7 @@ const Type = forwardRef(({ column, onChange }, ref) => {
}), [isPredefinedPropertiesOpen, isCustomPropertiesOpen]);
useEffect(() => {
onChange(COLUMNS.find(c => c.groupby === 'basics') || COLUMNS[0]);
// eslint-disable-next-line react-hooks/exhaustive-deps
setError('');
}, []);
return (
@@ -231,70 +50,20 @@ const Type = forwardRef(({ column, onChange }, ref) => {
tag="span"
className="sf-metadata-column-type-info"
>
<Icon iconName={column.icon} className="mr-2" />
<Icon symbol={column.icon} className="sf-metadata-icon mr-2" />
<span className="mr-auto">{column.name}</span>
<i className="sf3-font sf3-font-down" aria-hidden="true"></i>
</DropdownToggle>
<DropdownMenu
<CustomDropdownMenu
column={column}
modifiers={[{
name: 'offset',
options: {
offset: [0, 17],
}
}]}>
<div className="search-column-container">
<Input onChange={onSearchColumn} placeholder={gettext('Search properties')} value={searchValue} onClick={onSearchClick} ref={inputRef} />
</div>
{displayColumns.length > 0 && predefinedColumns.length > 0 && (
<>
{predefinedColumns.map(item => (
<DropdownItem
key={item.key}
className={classnames('column-type-item text-truncate', { 'active': item.key === column.key })}
onMouseEnter={() => setCustomPropertiesOpen(false)}
onClick={() => onSelectPredefinedColumn(item)}
>
<Icon iconName={item.icon} />
<span>{item.name}</span>
</DropdownItem>
))}
{basicsColumns.length > 0 && (
<>
<DropdownItem className="w-100" divider />
<Dropdown
className="w-100"
direction="end"
isOpen={isCustomPropertiesOpen}
toggle={toggleCustomProperties}
onMouseEnter={() => setCustomPropertiesOpen(true)}
onMouseMove={(e) => {e.stopPropagation();}}
>
<DropdownToggle
tag='span'
className="column-type-item dropdown-item text-truncate"
>
<Icon iconName="edit" />
<span className="mr-auto">{gettext('Custom properties')}</span>
<i className="sf3-font-down sf3-font rotate-270"></i>
</DropdownToggle>
<DropdownMenu>
{basicsColumns.map((item, index) => (
<DropdownItem
key={index}
className={classnames('column-type-item text-truncate', { 'active': item.key === column.key })}
onClick={() => onSelectCustomColumn(item)}
>
<Icon iconName={item.icon} />
<span>{item.name}</span>
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
</>
)}
</>
)}
</DropdownMenu>
}]}
onSelect={onChange}
/>
</Dropdown>
{error && (<FormFeedback>{error}</FormFeedback>)}
</FormGroup>

View File

@@ -1,14 +1,20 @@
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Dropdown, DropdownToggle } from 'reactstrap';
import PropTypes from 'prop-types';
import ColumnPopover from '../../../../../components/popover/column-popover';
import Icon from '../../../../../../components/icon';
import { isEnter } from '../../../../../../utils/hotkey';
import { getEventClassName } from '../../../../../../utils/dom';
import CustomDropdownMenu from '../../../../../components/popover/column-popover/custom-dropdown-menu';
import './index.css';
const InsertColumn = ({ lastColumn, height, groupOffsetLeft, insertColumn: insertColumnAPI }) => {
const [isColumnMenuOpen, setColumnMenuOpen] = useState(false);
const [isColumnPopoverShow, setColumnPopoverShow] = useState(false);
const [selectedColumn, setSelectedColumn] = useState(null);
const id = useMemo(() => 'sf-metadata-add-column', []);
const ref = useRef(null);
const style = useMemo(() => {
return {
height: height,
@@ -20,36 +26,73 @@ const InsertColumn = ({ lastColumn, height, groupOffsetLeft, insertColumn: inser
};
}, [lastColumn, height, groupOffsetLeft]);
const openPopover = useCallback(() => {
ref?.current?.click();
}, [ref]);
const insertColumn = useCallback((name, type, { key, data }) => {
const toggleAddColumn = useCallback(() => {
setColumnMenuOpen(!isColumnMenuOpen);
}, [isColumnMenuOpen]);
const handleCancel = useCallback(() => {
setColumnMenuOpen(false);
setColumnPopoverShow(false);
setSelectedColumn(null);
}, []);
const handleSubmit = useCallback((name, type, { key, data }) => {
insertColumnAPI(name, type, { key, data });
setColumnPopoverShow(false);
}, [insertColumnAPI]);
const onHotKey = useCallback((event) => {
if (isEnter(event) && document.activeElement && document.activeElement.id === id) {
openPopover();
}
}, [id, openPopover]);
const handleSelect = useCallback((column) => {
setSelectedColumn(column);
setColumnMenuOpen(false);
setColumnPopoverShow(true);
}, []);
const handleClickOutside = useCallback((event) => {
if (!isColumnPopoverShow) return;
if (!event.target) return;
const className = getEventClassName(event);
if (className.indexOf('column-type-item') > -1) return;
const popover = document.querySelector('.sf-metadata-column-popover');
if ((popover && popover.contains(event.target))) return;
setColumnPopoverShow(false);
}, [isColumnPopoverShow]);
useEffect(() => {
document.addEventListener('keydown', onHotKey);
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('keydown', onHotKey);
document.removeEventListener('mousedown', handleClickOutside);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
});
return (
<>
<div className="sf-metadata-record-header-cell">
<div className="sf-metadata-result-table-cell column insert-column" style={style} id={id} ref={ref}>
<Dropdown
id={id}
className="sf-metadata-record-header-cell sf-metadata-column-type"
isOpen={isColumnMenuOpen}
direction="down"
toggle={toggleAddColumn}
style={style}
>
<DropdownToggle
tag="span"
className="sf-metadata-result-table-cell column insert-column"
>
<Icon symbol="add-table" />
</div>
</div>
<ColumnPopover target={id} onChange={insertColumn} />
</DropdownToggle>
<CustomDropdownMenu onSelect={handleSelect} />
</Dropdown>
{isColumnPopoverShow && !isColumnMenuOpen && (
<ColumnPopover
target={id}
column={selectedColumn}
onSelect={handleSelect}
onCancel={handleCancel}
onSubmit={handleSubmit}
/>
)}
</>
);
};