mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-26 15:26:19 +00:00
refactor(metadata): remove ui-component (#7492)
1. ModalPortal 2. Icon 3. IconBtn 4. Loading 5. CenteredLoading 6. ClickOutside 7. SearchInput 8. Switch 9. CustomizeAddTool 10. SfCalendar 11. SfFilterCalendar 12. CustomizeSelect 13. CustomizePopover 14. FieldDisplaySettings 15. Formatters 16. remove duplicate codes
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
.seafile-option-group {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
min-height: 60px;
|
||||
max-height: 300px;
|
||||
min-width: 100%;
|
||||
max-width: 15rem;
|
||||
padding: 0.5rem 0;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||
border-radius: 3px;
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
.seafile-option-group .seafile-option-group-search {
|
||||
width: 100%;
|
||||
padding: 0 10px 6px 10px;
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
.seafile-option-group-search .form-control {
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
.seafile-option-group .none-search-result {
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.seafile-option-group .seafile-option-group-content {
|
||||
max-height: 252px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.seafile-select-option {
|
||||
display: block;
|
||||
width: 100%;
|
||||
line-height: 24px;
|
||||
padding: 0.25rem 10px;
|
||||
clear: both;
|
||||
font-weight: 400;
|
||||
text-align: inherit;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.seafile-select-option.seafile-select-option-active {
|
||||
background-color: #20a0ff;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.seafile-select-option.seafile-select-option-active .select-option-name {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.seafile-select-option:hover .header-icon .seafile-multicolor-icon,
|
||||
.seafile-select-option.seafile-select-option-active .header-icon .seafile-multicolor-icon {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.seafile-select-option:not(.seafile-select-option-active):hover .header-icon .seafile-multicolor-icon {
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
.seafile-select-option .select-option-name .single-select-option {
|
||||
margin: 0 0 0 12px;
|
||||
}
|
||||
|
||||
.seafile-select-option .select-option-name .multiple-select-option {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.seafile-option-group-selector-single-select .select-option-name,
|
||||
.seafile-option-group-selector-multiple-select .multiple-option-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.seafile-option-group-selector-multiple-select .multiple-check-icon {
|
||||
display: inline-flex;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.seafile-option-group-selector-multiple-select .multiple-check-icon .seafile-multicolor-icon-check-mark {
|
||||
font-size: 12px;
|
||||
color: #798d99;
|
||||
}
|
||||
|
||||
.seafile-option-group-selector-single-select .seafile-select-option:hover,
|
||||
.seafile-option-group-selector-single-select .seafile-select-option.seafile-select-option-active,
|
||||
.seafile-option-group-selector-multiple-select .seafile-select-option:hover,
|
||||
.seafile-option-group-selector-multiple-select .seafile-select-option.seafile-select-option-active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
@@ -0,0 +1,233 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import ClickOutside from '../../click-outside';
|
||||
import SearchInput from '../../search-input';
|
||||
import Option from './option';
|
||||
import { KeyCodes } from '../../../constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const OPTION_HEIGHT = 32;
|
||||
|
||||
class SelectOptionGroup extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
searchVal: '',
|
||||
activeIndex: -1,
|
||||
disableHover: false,
|
||||
};
|
||||
this.filterOptions = null;
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('keydown', this.onHotKey);
|
||||
setTimeout(() => {
|
||||
this.resetMenuStyle();
|
||||
}, 1);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.filterOptions = null;
|
||||
this.timer && clearTimeout(this.timer);
|
||||
window.removeEventListener('keydown', this.onHotKey);
|
||||
}
|
||||
|
||||
resetMenuStyle = () => {
|
||||
const { isInModal, position } = this.props;
|
||||
const { top, height } = this.optionGroupRef.getBoundingClientRect();
|
||||
if (isInModal) {
|
||||
if (position.y + position.height + height > window.innerHeight) {
|
||||
this.optionGroupRef.style.top = (position.y - height) + 'px';
|
||||
}
|
||||
this.optionGroupRef.style.opacity = 1;
|
||||
}
|
||||
else {
|
||||
if (height + top > window.innerHeight) {
|
||||
const borderWidth = 2;
|
||||
this.optionGroupRef.style.top = -1 * (height + borderWidth) + 'px';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onHotKey = (event) => {
|
||||
const keyCode = event.keyCode;
|
||||
if (keyCode === KeyCodes.UpArrow) {
|
||||
this.onPressUp();
|
||||
} else if (keyCode === KeyCodes.DownArrow) {
|
||||
this.onPressDown();
|
||||
} else if (keyCode === KeyCodes.Enter) {
|
||||
let option = this.filterOptions && this.filterOptions[this.state.activeIndex];
|
||||
if (option) {
|
||||
this.props.onSelectOption(option.value);
|
||||
if (!this.props.supportMultipleSelect) {
|
||||
this.props.closeSelect();
|
||||
}
|
||||
}
|
||||
} else if (keyCode === KeyCodes.Tab || keyCode === KeyCodes.Escape) {
|
||||
this.props.closeSelect();
|
||||
}
|
||||
};
|
||||
|
||||
onPressUp = () => {
|
||||
if (this.state.activeIndex > 0) {
|
||||
this.setState({ activeIndex: this.state.activeIndex - 1 }, () => {
|
||||
this.scrollContent();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onPressDown = () => {
|
||||
if (this.filterOptions && this.state.activeIndex < this.filterOptions.length - 1) {
|
||||
this.setState({ activeIndex: this.state.activeIndex + 1 }, () => {
|
||||
this.scrollContent();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onMouseDown = (e) => {
|
||||
const { isInModal } = this.props;
|
||||
// prevent event propagation when click option or search input
|
||||
if (isInModal) {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
}
|
||||
};
|
||||
|
||||
scrollContent = () => {
|
||||
const { offsetHeight, scrollTop } = this.optionGroupContentRef;
|
||||
this.setState({ disableHover: true });
|
||||
this.timer = setTimeout(() => {
|
||||
this.setState({ disableHover: false });
|
||||
}, 500);
|
||||
if (this.state.activeIndex * OPTION_HEIGHT === 0) {
|
||||
this.optionGroupContentRef.scrollTop = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.activeIndex * OPTION_HEIGHT < scrollTop) {
|
||||
this.optionGroupContentRef.scrollTop = scrollTop - OPTION_HEIGHT;
|
||||
}
|
||||
else if (this.state.activeIndex * OPTION_HEIGHT > offsetHeight + scrollTop) {
|
||||
this.optionGroupContentRef.scrollTop = scrollTop + OPTION_HEIGHT;
|
||||
}
|
||||
};
|
||||
|
||||
changeIndex = (index) => {
|
||||
this.setState({ activeIndex: index });
|
||||
};
|
||||
|
||||
onChangeSearch = (searchVal) => {
|
||||
let value = searchVal || '';
|
||||
if (value !== this.state.searchVal) {
|
||||
this.setState({ searchVal: value, activeIndex: -1, });
|
||||
}
|
||||
};
|
||||
|
||||
renderOptGroup = (searchVal) => {
|
||||
let { noOptionsPlaceholder, onSelectOption } = this.props;
|
||||
this.filterOptions = this.props.getFilterOptions(searchVal);
|
||||
if (this.filterOptions.length === 0) {
|
||||
return (
|
||||
<div className="none-search-result">{noOptionsPlaceholder}</div>
|
||||
);
|
||||
}
|
||||
return this.filterOptions.map((opt, i) => {
|
||||
let key = opt.value.column ? opt.value.column.key : i;
|
||||
let isActive = this.state.activeIndex === i;
|
||||
return (
|
||||
<Option
|
||||
key={key}
|
||||
index={i}
|
||||
isActive={isActive}
|
||||
value={opt.value}
|
||||
onSelectOption={onSelectOption}
|
||||
changeIndex={this.changeIndex}
|
||||
supportMultipleSelect={this.props.supportMultipleSelect}
|
||||
disableHover={this.state.disableHover}
|
||||
>
|
||||
{opt.label}
|
||||
</Option>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { searchable, searchPlaceholder, top, left, minWidth, value, isShowSelected, isInModal, position,
|
||||
className, addOptionAble, component } = this.props;
|
||||
const { AddOption } = component || {};
|
||||
let { searchVal } = this.state;
|
||||
let style = { top: top || 0, left: left || 0 };
|
||||
if (minWidth) {
|
||||
style = { top: top || 0, left: left || 0, minWidth };
|
||||
}
|
||||
if (isInModal) {
|
||||
style = {
|
||||
position: 'fixed',
|
||||
left: position.x,
|
||||
top: position.y + position.height,
|
||||
minWidth: position.width,
|
||||
opacity: 0,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<ClickOutside onClickOutside={this.props.onClickOutside}>
|
||||
<div
|
||||
className={classnames('seafile-option-group', className ? 'seafile-option-group-' + className : '', {
|
||||
'pt-0': isShowSelected,
|
||||
'create-new-seafile-option-group': addOptionAble,
|
||||
})}
|
||||
ref={(ref) => this.optionGroupRef = ref}
|
||||
style={style}
|
||||
onMouseDown={this.onMouseDown}
|
||||
>
|
||||
{isShowSelected &&
|
||||
<div className="editor-list-delete mb-2" onClick={(e) => e.stopPropagation()}>{value.label || ''}</div>
|
||||
}
|
||||
{searchable && (
|
||||
<div className="seafile-option-group-search">
|
||||
<SearchInput
|
||||
className="option-search-control"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={this.onChangeSearch}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="seafile-option-group-content" ref={(ref) => this.optionGroupContentRef = ref}>
|
||||
{this.renderOptGroup(searchVal)}
|
||||
</div>
|
||||
{addOptionAble && AddOption}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectOptionGroup.propTypes = {
|
||||
top: PropTypes.number,
|
||||
left: PropTypes.number,
|
||||
minWidth: PropTypes.number,
|
||||
options: PropTypes.array,
|
||||
onSelectOption: PropTypes.func,
|
||||
searchable: PropTypes.bool,
|
||||
addOptionAble: PropTypes.bool,
|
||||
component: PropTypes.object,
|
||||
searchPlaceholder: PropTypes.string,
|
||||
noOptionsPlaceholder: PropTypes.string,
|
||||
onClickOutside: PropTypes.func.isRequired,
|
||||
closeSelect: PropTypes.func.isRequired,
|
||||
getFilterOptions: PropTypes.func.isRequired,
|
||||
supportMultipleSelect: PropTypes.bool,
|
||||
value: PropTypes.object,
|
||||
isShowSelected: PropTypes.bool,
|
||||
stopClickEvent: PropTypes.bool,
|
||||
isInModal: PropTypes.bool,
|
||||
position: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default SelectOptionGroup;
|
@@ -0,0 +1,50 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
class Option extends Component {
|
||||
|
||||
onSelectOption = (value, event) => {
|
||||
if (this.props.supportMultipleSelect) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.props.onSelectOption(value, event);
|
||||
};
|
||||
|
||||
onMouseEnter = () => {
|
||||
if (!this.props.disableHover) {
|
||||
this.props.changeIndex(this.props.index);
|
||||
}
|
||||
};
|
||||
|
||||
onMouseLeave = () => {
|
||||
if (!this.props.disableHover) {
|
||||
this.props.changeIndex(-1);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={classnames('seafile-select-option', { 'seafile-select-option-active': this.props.isActive })}
|
||||
onClick={this.onSelectOption.bind(this, this.props.value)}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Option.propTypes = {
|
||||
index: PropTypes.number,
|
||||
isActive: PropTypes.bool,
|
||||
changeIndex: PropTypes.func,
|
||||
value: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
|
||||
onSelectOption: PropTypes.func,
|
||||
supportMultipleSelect: PropTypes.bool,
|
||||
disableHover: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Option;
|
Reference in New Issue
Block a user