1
0
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:
Jerry Ren
2025-03-01 10:12:48 +08:00
committed by GitHub
parent 67083238c2
commit 890880a5c8
281 changed files with 3523 additions and 1271 deletions

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;