1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-19 18:29:23 +00:00

Improve add view UI (#6529)

* improve add view and rename view ui

update view popover

format code

remove redundant code

improve input ui in view popover

remove redundant code

update rename view ui

* update icon size and color

* update icon color

---------

Co-authored-by: Michael An <2331806369@qq.com>
This commit is contained in:
Aries
2024-08-14 13:37:52 +08:00
committed by GitHub
parent 16186b2a8b
commit ae47175713
7 changed files with 312 additions and 33 deletions

View File

@@ -22,9 +22,10 @@
}
.metadata-tree-view .metadata-views-icon {
height: 14px;
width: 14px;
height: 1rem;
width: 1rem;
line-height: 1.5;
color: #666;
}
.metadata-tree-view .sf-metadata-add-view {
@@ -56,3 +57,11 @@
line-height: 24px;
font-weight: 400;
}
.metadata-tree-view .sf-metadata-view-input {
width: 100%;
height: 28px;
padding: 0;
position: relative;
text-align: center;
}

View File

@@ -1,11 +1,13 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { CustomizeAddTool } from '@seafile/sf-metadata-ui-component';
import { gettext } from '../../utils/constants';
import { PRIVATE_FILE_TYPE } from '../../constants';
import ViewItem from './view-item';
import NameDialog from './name-dialog';
import { useMetadata } from '../hooks';
import { Form, Input } from 'reactstrap';
import Icon from '../../components/icon';
import { AddView } from '../metadata-view/components/popover/view-popover';
import './index.css';
@@ -14,7 +16,6 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
if (userPerm !== 'rw' && userPerm !== 'admin') return false;
return true;
}, [userPerm]);
const [showAddViewDialog, setSowAddViewDialog] = useState(false);
const [, setState] = useState(0);
const {
showFirstView,
@@ -27,6 +28,11 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
moveView
} = useMetadata();
const [showAddViewPopover, setShowAddViewPopover] = useState(false);
const [showInput, setShowInput] = useState(false);
const inputRef = useRef(null);
const [inputValue, setInputValue] = useState('Untitled');
useEffect(() => {
const { origin, pathname, search } = window.location;
const urlParams = new URLSearchParams(search);
@@ -49,14 +55,6 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const openAddView = useCallback(() => {
setSowAddViewDialog(true);
}, []);
const closeAddView = useCallback(() => {
setSowAddViewDialog(false);
}, []);
const onUpdateView = useCallback((viewId, update, successCallback, failCallback) => {
updateView(viewId, update, () => {
setState(n => n + 1);
@@ -64,6 +62,46 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
}, failCallback);
}, [updateView]);
const togglePopover = (event) => {
event.stopPropagation();
setShowAddViewPopover(!showAddViewPopover);
};
const handleInputChange = (event) => {
setInputValue(event.target.value);
};
const handlePopoverOptionClick = () => {
setShowInput(true);
setShowAddViewPopover(false);
};
const handleInputSubmit = useCallback((event) => {
event.preventDefault();
event.stopPropagation();
addView(inputValue);
setShowInput(false);
setInputValue('Untitled');
}, [inputValue, addView]);
const handleClickOutsideInput = useCallback((event) => {
if (inputRef.current && !inputRef.current.contains(event.target)) {
setShowInput(false);
}
}, []);
useEffect(() => {
if (showInput) {
inputRef.current.select();
document.addEventListener('click', handleClickOutsideInput);
}
return () => {
document.removeEventListener('click', handleClickOutsideInput);
};
}, [showInput, inputRef, handleClickOutsideInput]);
return (
<>
<div className="tree-view tree metadata-tree-view">
@@ -86,18 +124,41 @@ const MetadataTreeView = ({ userPerm, currentPath }) => {
onMove={moveView}
/>);
})}
{canAdd &&
{showInput && (
<Form onSubmit={handleInputSubmit} className='tree-view-inner sf-metadata-view-form'>
<div className="left-icon">
<div className="tree-node-icon">
<Icon symbol="table" className="metadata-views-icon" />
</div>
</div>
<Input
className='sf-metadata-view-input'
innerRef={inputRef}
type='text'
id='add-view-input'
name='add-view'
value={inputValue}
onChange={handleInputChange}
autoFocus={true}
/>
</Form>
)}
{canAdd && (
<div id="sf-metadata-view-popover">
<CustomizeAddTool
className="sf-metadata-add-view"
callBack={openAddView}
callBack={togglePopover}
footerName={gettext('Add view')}
addIconClassName="sf-metadata-add-view-icon"
/>
}
</div>
)}
</div>
</div>
</div>
{showAddViewDialog && (<NameDialog title={gettext('Add view')} onSubmit={addView} onToggle={closeAddView} />)}
{showAddViewPopover && (
<AddView target='sf-metadata-view-popover' toggle={togglePopover} onOptionClick={handlePopoverOptionClick} />
)}
</>
);
};

View File

@@ -4,12 +4,11 @@ import classnames from 'classnames';
import { gettext } from '../../../utils/constants';
import Icon from '../../../components/icon';
import ItemDropdownMenu from '../../../components/dropdown-menu/item-dropdown-menu';
import NameDialog from '../name-dialog';
import { Rename } from '../../metadata-view/components/popover/view-popover';
import { Utils, isMobile } from '../../../utils/utils';
import './index.css';
const ViewItem = ({
canDelete,
userPerm,
@@ -22,8 +21,8 @@ const ViewItem = ({
}) => {
const [highlight, setHighlight] = useState(false);
const [freeze, setFreeze] = useState(false);
const [isShowRenameDialog, setRenameDialogShow] = useState(false);
const [isDropShow, setDropShow] = useState(false);
const [isShowRenamePopover, setRenamePopoverShow] = useState(false);
const canUpdate = useMemo(() => {
if (userPerm !== 'rw' && userPerm !== 'admin') return false;
@@ -72,7 +71,7 @@ const ViewItem = ({
const operationClick = useCallback((operationKey) => {
if (operationKey === 'rename') {
setRenameDialogShow(true);
setRenamePopoverShow(true);
return;
}
@@ -82,13 +81,14 @@ const ViewItem = ({
}
}, [onDelete]);
const closeRenameDialog = useCallback(() => {
setRenameDialogShow(false);
const closeRenamePopover = useCallback((event) => {
event.stopPropagation();
setRenamePopoverShow(false);
}, []);
const renameView = useCallback((name, failCallback) => {
onUpdate({ name }, () => {
setRenameDialogShow(false);
setRenamePopoverShow(false);
}, failCallback);
}, [onUpdate]);
@@ -154,7 +154,7 @@ const ViewItem = ({
<Icon symbol="table" className="metadata-views-icon" />
</div>
</div>
<div className="right-icon">
<div className="right-icon" id={`metadata-view-dropdown-item-${view._id}`} >
{highlight && (
<ItemDropdownMenu
item={{ name: 'metadata-view' }}
@@ -168,11 +168,10 @@ const ViewItem = ({
)}
</div>
</div>
{isShowRenameDialog && (
<NameDialog title={gettext('Rename view')} value={view.name} onSubmit={renameView} onToggle={closeRenameDialog} />
{isShowRenamePopover && (
<Rename value={view.name} target={`metadata-view-dropdown-item-${view._id}`} toggle={closeRenamePopover} onSubmit={renameView} />
)}
</>
);
};

View File

@@ -0,0 +1,62 @@
import React, { useRef, useEffect, useCallback } from 'react';
import { UncontrolledPopover } from 'reactstrap';
import PropTypes from 'prop-types';
import { gettext } from '../../../../utils';
import Icon from '../../../../../../components/icon';
import '../index.css';
const AddView = ({ target, toggle, onOptionClick }) => {
const popoverRef = useRef(null);
const handleClickOutside = useCallback((event) => {
if (popoverRef.current && !popoverRef.current.contains(event.target)) {
toggle(event);
}
}, [toggle]);
useEffect(() => {
if (popoverRef.current) {
document.addEventListener('click', handleClickOutside, true);
}
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
}, [handleClickOutside]);
return (
<UncontrolledPopover
className='sf-metadata-addview-popover'
isOpen={true}
toggle={toggle}
target={target}
placement='right-start'
hideArrow={true}
fade={false}
boundariesElement={document.body}
>
<div ref={popoverRef}>
<div className='sf-metadata-addview-popover-header'>{gettext('New view')}</div>
<div className='sf-metadata-addview-popover-body'>
<button className='dropdown-item sf-metadata-addview-popover-item' onClick={onOptionClick}>
<div className="left-icon">
<div className="metadata-view-icon">
<Icon symbol="table" />
</div>
</div>
<div>{gettext('Table')}</div>
</button>
</div>
</div>
</UncontrolledPopover>
);
};
AddView.propTypes = {
target: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired,
onOptionClick: PropTypes.func.isRequired,
};
export default AddView;

View File

@@ -0,0 +1,68 @@
.sf-metadata-rename-view-popover .popover,
.sf-metadata-addview-popover .popover {
max-width: none;
min-width: 280px;
margin: 0;
padding: 0.5rem 0;
border: 0;
}
.sf-metadata-view-form {
display: flex;
padding-left: 0.5rem;
gap: 0.5rem;
}
.sf-metadata-addview-popover .sf-metadata-addview-popover-header {
width: 100%;
height: 2rem;
padding: 0 1rem;
display: flex;
align-items: center;
justify-content: left;
color: #666;
opacity: 1;
font-size: 0.875rem;
}
.sf-metadata-rename-view-popover .sf-metadata-rename-view-popover-header {
width: 100%;
height: 1.5rem;
padding: 0 1rem;
display: flex;
align-items: center;
justify-content: left;
color: #666;
opacity: 1;
font-size: 0.875rem;
}
.sf-metadata-addview-popover
.sf-metadata-addview-popover-body {
width: 100%;
display: flex;
flex-direction: column;
}
.dropdown-item.sf-metadata-addview-popover-item {
width: 100%;
height: 2rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.dropdown-item.sf-metadata-addview-popover-item .metadata-view-icon {
display: flex;
align-items: center;
font-size: 1rem;
color: #666;
}
.sf-metadata-rename-view-popover-body {
padding: 0 0.5rem;
}
.dropdown-item:hover .metadata-view-icon {
color: #fff;
}

View File

@@ -0,0 +1,2 @@
export { default as Rename } from './rename';
export { default as AddView } from './add-view';

View File

@@ -0,0 +1,78 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Form, Input, UncontrolledPopover } from 'reactstrap';
import PropTypes from 'prop-types';
import { gettext } from '../../../../utils';
import '../index.css';
const Rename = ({ value, target, toggle, onSubmit }) => {
const [inputValue, setInputValue] = useState(value || '');
const inputRef = useRef(null);
const onChange = useCallback((e) => {
setInputValue(e.target.value);
}, []);
const handleSubmit = useCallback((e) => {
e.preventDefault();
e.stopPropagation();
onSubmit(inputValue);
}, [inputValue, onSubmit]);
useEffect(() => {
const handleClickOutSide = (event) => {
if (inputRef.current && !inputRef.current.contains(event.target)) {
toggle(event);
}
};
if (inputRef.current) {
inputRef.current.select();
document.addEventListener('mousedown', handleClickOutSide);
}
return () => {
document.removeEventListener('mousedown', handleClickOutSide);
};
}, [toggle]);
return (
<UncontrolledPopover
className='sf-metadata-rename-view-popover'
isOpen={true}
toggle={toggle}
target={target}
placement='right-start'
hideArrow={true}
fade={false}
boundariesElement={document.body}
>
<div className='sf-metadata-rename-view-popover-header'>
{gettext('Rename view')}
</div>
<div className="seafile-divider dropdown-divider"></div>
<div className='sf-metadata-rename-view-popover-body'>
<Form onSubmit={handleSubmit}>
<Input
innerRef={inputRef}
className='sf-metadata-view-input'
type='text'
id="rename-input"
name='rename'
value={inputValue}
onChange={onChange}
autoFocus={true}
/>
</Form>
</div>
</UncontrolledPopover>
);
};
Rename.propTypes = {
value: PropTypes.string,
target: PropTypes.string.isRequired,
toggle: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
};
export default Rename;