新版ui合并到web中。

This commit is contained in:
tengge1 2019-07-14 09:51:35 +08:00
parent 38c4c88a6f
commit 59a90678f9
114 changed files with 4936 additions and 0 deletions

View File

@ -0,0 +1,34 @@
:root {
--theme-blue: #3399FF;
--theme-green: #64CF40;
--theme-orange: #F6A623;
--theme-black: #FFFFFF;
user-select: none;
}
:focus {
outline: none;
}
::-webkit-scrollbar {
width: 0.2em;
}
::-webkit-scrollbar:horizontal {
height: 0.5em;
}
::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: rgba(0, 0, 0, 0.3);
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
}
::-webkit-scrollbar-thumb:window-inactive {
background: rgba(169, 169, 169, 0.4);
}

View File

@ -0,0 +1,36 @@
import './css/Canvas.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 画布
* @author tengge / https://github.com/tengge1
*/
class Canvas extends React.Component {
constructor(props) {
super(props);
this.dom = React.createRef();
}
render() {
const { className, style, ...others } = this.props;
return <canvas
className={classNames('Canvas', className)}
style={style}
ref={this.dom}
{...others}></canvas>;
}
}
Canvas.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
};
Canvas.defaultProps = {
className: null,
style: null,
};
export default Canvas;

View File

@ -0,0 +1,11 @@
/**
* 折叠面板
* @author tengge / https://github.com/tengge1
*/
class Accordion extends React.Component {
render() {
return null;
}
}
export default Accordion;

View File

@ -0,0 +1,11 @@
/**
* 很多按钮
* @author tengge / https://github.com/tengge1
*/
class Buttons extends React.Component {
render() {
return null;
}
}
export default Buttons;

View File

@ -0,0 +1,11 @@
/**
*
* @author tengge / https://github.com/tengge1
*/
class Column extends React.Component {
render() {
return null;
}
}
export default Column;

View File

@ -0,0 +1,11 @@
/**
* 很多列
* @author tengge / https://github.com/tengge1
*/
class Columns extends React.Component {
render() {
return null;
}
}
export default Columns;

View File

@ -0,0 +1,11 @@
/**
* 内容
* @author tengge / https://github.com/tengge1
*/
class Content extends React.Component {
render() {
return null;
}
}
export default Content;

View File

@ -0,0 +1,11 @@
/**
*
* @author tengge / https://github.com/tengge1
*/
class Row extends React.Component {
render() {
return null;
}
}
export default Row;

View File

@ -0,0 +1,11 @@
/**
* 很多行
* @author tengge / https://github.com/tengge1
*/
class Rows extends React.Component {
render() {
return null;
}
}
export default Rows;

View File

@ -0,0 +1,38 @@
import './css/Button.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 按钮
* @author tengge / https://github.com/tengge1
*/
class Button extends React.Component {
render() {
const { className, style, children, color, disabled, ...others } = this.props;
return <button
className={classNames('Button', color, disabled && 'disabled', className)}
style={style}
disabled={disabled}
{...others}>
{children}
</button>;
}
}
Button.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
color: PropTypes.oneOf(['primary', 'success', 'warn', 'danger']),
disabled: PropTypes.bool,
};
Button.defaultProps = {
className: null,
style: null,
children: null,
color: null,
disabled: false,
};
export default Button;

View File

@ -0,0 +1,61 @@
import './css/CheckBox.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 复选框
* @author tengge / https://github.com/tengge1
*/
class CheckBox extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: props.checked,
};
this.handleChange = this.handleChange.bind(this, props.onChange);
}
handleChange(onChange, event) {
const target = event.target;
const name = target.getAttribute('name');
const checked = target.checked;
this.setState({ checked });
onChange && onChange(name, checked, event);
}
render() {
const { className, style, name, checked, disabled, onChange } = this.props;
return <input
type={'checkbox'}
className={classNames('CheckBox', this.state.checked && 'checked', disabled && 'disabled', className)}
style={style}
name={name}
defaultChecked={this.state.checked}
disabled={disabled}
onClick={this.handleChange} />;
}
}
CheckBox.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
name: PropTypes.string,
checked: PropTypes.bool,
disabled: PropTypes.bool,
onChange: PropTypes.func,
};
CheckBox.defaultProps = {
className: null,
style: null,
name: undefined,
checked: false,
disabled: false,
onChange: null,
};
export default CheckBox;

View File

@ -0,0 +1,51 @@
import './css/Form.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 表单
* @author tengge / https://github.com/tengge1
*/
class Form extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this, props.onSubmit);
}
handleSubmit(onSubmit) {
event.preventDefault();
onSubmit && onSubmit();
}
render() {
const { className, style, children, direction, onSubmit, ...others } = this.props;
return <form
className={classNames('Form', direction, className)}
style={style}
onSubmit={this.handleSubmit}
{...others}>
{children}
</form>;
}
}
Form.propTypes = {
onSubmit: PropTypes.func,
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
direction: PropTypes.oneOf(['horizontal', 'vertical']),
};
Form.defaultProps = {
onSubmit: null,
className: null,
style: null,
children: null,
direction: 'horizontal',
};
export default Form;

View File

@ -0,0 +1,31 @@
import './css/FormControl.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 表单项
* @author tengge / https://github.com/tengge1
*/
class FormControl extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <div className={classNames('FormControl', className)} style={style} {...others}>
{children}
</div>;
}
}
FormControl.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
FormControl.defaultProps = {
className: null,
style: null,
children: null,
};
export default FormControl;

View File

@ -0,0 +1,41 @@
import './css/IconButton.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 图标按钮
* @author tengge / https://github.com/tengge1
*/
class IconButton extends React.Component {
render() {
const { className, style, icon, title, selected, onClick, ...others } = this.props;
return <button
className={classNames('IconButton', selected && 'selected', className)}
style={style}
title={title}
onClick={onClick}
{...others}>
<i className={classNames('iconfont', icon && 'icon-' + icon)}></i>
</button>;
}
}
IconButton.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
icon: PropTypes.string,
title: PropTypes.string,
selected: PropTypes.bool,
onClick: PropTypes.func,
};
IconButton.defaultProps = {
className: null,
style: null,
icon: null,
title: null,
selected: false,
onClick: null,
};
export default IconButton;

View File

@ -0,0 +1,56 @@
import './css/Input.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 输入框
* @author tengge / https://github.com/tengge1
*/
class Input extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this, props.onChange);
this.handleInput = this.handleInput.bind(this, props.onInput);
}
handleChange(onChange, event) {
onChange && onChange(event.target.value, event);
}
handleInput(onInput, event) {
onInput && onInput(event.target.value, event);
}
render() {
const { className, style, value, disabled, onChange, onInput } = this.props;
return <input
className={classNames('Input', className)}
style={style}
value={value}
onChange={this.handleChange}
onInput={this.handleInput}
disabled={disabled} />;
}
}
Input.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
value: PropTypes.string,
disabled: PropTypes.bool,
onChange: PropTypes.func,
onInput: PropTypes.func,
};
Input.defaultProps = {
className: null,
style: null,
value: '',
disabled: false,
onChange: null,
onInput: null,
};
export default Input;

View File

@ -0,0 +1,31 @@
import './css/Label.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 标签
* @author tengge / https://github.com/tengge1
*/
class Label extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <label className={classNames('Label', className)} style={style} {...others}>
{children}
</label>;
}
}
Label.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
Label.defaultProps = {
className: null,
style: null,
children: null,
};
export default Label;

View File

@ -0,0 +1,59 @@
import './css/Radio.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 单选框
* @author tengge / https://github.com/tengge1
*/
class Radio extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: props.selected,
};
this.handleChange = this.handleChange.bind(this, props.onChange);
}
handleChange(onChange, event) {
this.setState({
selected: event.target.checked,
});
onChange && onChange(event.target.checked, event);
}
render() {
const { className, style, selected, disabled, onChange, ...others } = this.props;
return <input
type={'radio'}
className={classNames('Radio',
this.state.selected && 'selected',
disabled && 'disabled',
className)}
style={style}
defaultChecked={this.state.selected}
disabled={disabled}
onClick={this.handleChange}
{...others} />;
}
}
Radio.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
selected: PropTypes.bool,
disabled: PropTypes.bool,
onChange: PropTypes.func,
};
Radio.defaultProps = {
className: null,
style: null,
selected: false,
disabled: false,
onChange: null,
};
export default Radio;

View File

@ -0,0 +1,156 @@
import './css/SearchField.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
import IconButton from './IconButton.jsx';
import CheckBox from './CheckBox.jsx';
/**
* 搜索框
* @author tengge / https://github.com/tengge1
*/
class SearchField extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value,
categories: [],
filterShow: false,
};
this.handleAdd = this.handleAdd.bind(this, props.onAdd);
this.handleChange = this.handleChange.bind(this, props.onChange);
this.handleInput = this.handleInput.bind(this, props.onInput);
this.handleReset = this.handleReset.bind(this, props.onInput, props.onChange);
this.handleShowFilter = this.handleShowFilter.bind(this);
this.handleCheckBoxChange = this.handleCheckBoxChange.bind(this, props.onInput, props.onChange);
}
render() {
const { className, style, data, placeholder, addHidden } = this.props;
const { value, categories, filterShow } = this.state;
return <div className={classNames('SearchField', className)}>
<IconButton
className={classNames(addHidden && 'hidden')}
icon={'add'}
onClick={this.handleAdd}></IconButton>
<input
className={'input'}
style={style}
placeholder={placeholder}
value={value}
onChange={this.handleChange}
onInput={this.handleInput}
onKeyDown={this.handleKeyDown}
/>
<IconButton
icon={'close'}
onClick={this.handleReset}></IconButton>
<IconButton
icon={'filter'}
className={classNames(filterShow && 'selected')}
onClick={this.handleShowFilter}></IconButton>
<div className={classNames('category', !filterShow && 'hidden')}>
{data.map(n => {
return <div className={'item'} key={n.ID}>
<CheckBox
name={n.ID}
checked={categories.indexOf(n.ID) > -1}
onChange={this.handleCheckBoxChange}></CheckBox>
<label className={'title'}>{n.Name}</label>
</div>;
})}
</div>
</div>;
}
handleAdd(onAdd, event) {
onAdd && onAdd(event);
}
handleChange(onChange, event) {
event.stopPropagation();
const value = event.target.value;
this.setState({ value });
onChange && onChange(value, this.state.categories, event);
}
handleInput(onInput, event) {
event.stopPropagation();
const value = event.target.value;
this.setState({ value });
onInput && onInput(value, this.state.categories, event);
}
handleReset(onInput, onChange, event) {
const value = '';
this.setState({ value });
onInput && onInput(value, this.state.categories, event);
onChange && onChange(value, this.state.categories, event);
}
handleShowFilter() {
this.setState({
filterShow: !this.state.filterShow,
});
}
handleCheckBoxChange(onInput, onChange, name, checked, event) {
let categories = this.state.categories;
let index = categories.indexOf(name);
if (checked && index === -1) {
categories.push(name);
} else if (!checked && index > -1) {
categories.splice(index, 1);
} else {
console.warn(`SearchField: handleCheckBoxChange error.`);
return;
}
const value = this.state.value;
this.setState({ categories }, () => {
onInput && onInput(value, categories, event);
onChange && onChange(value, categories, event);
});
}
}
SearchField.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
value: PropTypes.string,
data: PropTypes.array,
placeholder: PropTypes.string,
onAdd: PropTypes.func,
onChange: PropTypes.func,
onInput: PropTypes.func,
handleShowFilter: PropTypes.func,
addHidden: PropTypes.bool,
};
SearchField.defaultProps = {
className: null,
style: null,
value: '',
data: [],
placeholder: 'Enter a keyword',
onAdd: null,
onChange: null,
onInput: null,
handleShowFilter: null,
addHidden: false,
};
export default SearchField;

View File

@ -0,0 +1,63 @@
import './css/Select.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 输入框
* @author tengge / https://github.com/tengge1
*/
class Select extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this, props.onChange);
}
handleChange(onChange, event) {
const selectedIndex = event.target.selectedIndex;
if (selectedIndex === -1) {
onChange && onChange(null, event);
return;
}
const value = event.target.options[selectedIndex].value;
onChange && onChange(value, event);
}
render() {
const { className, style, options, value, disabled, onChange } = this.props;
return <select
className={classNames('Select', className)}
style={style}
value={value}
disabled={disabled}
onChange={this.handleChange}>
{options && Object.keys(options).map(n => {
return <option value={n} key={n}>{options[n]}</option>;
})}
</select>;
}
}
Select.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
options: PropTypes.object,
value: PropTypes.string,
disabled: PropTypes.bool,
onChange: PropTypes.func,
};
Select.defaultProps = {
className: null,
style: null,
options: null,
value: null,
disabled: false,
onChange: null,
};
export default Select;

View File

@ -0,0 +1,64 @@
import './css/TextArea.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 文本域
* @author tengge / https://github.com/tengge1
*/
class TextArea extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value,
};
this.handleChange = this.handleChange.bind(this, props.onChange);
this.handleInput = this.handleInput.bind(this, props.onInput);
}
handleChange(onChange, event) {
this.setState({
value: event.target.value,
});
onChange && onChange(event.target.value, event);
}
handleInput(onInput, event) {
this.setState({
value: event.target.value,
});
onInput && onInput(event.target.value, event);
}
render() {
const { className, style, value, onChange, onInput, ...others } = this.props;
return <textarea
className={classNames('TextArea', className)}
style={style}
value={this.state.value}
onChange={this.handleChange}
onInput={this.handleInput}
{...others}></textarea>;
}
}
TextArea.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
value: PropTypes.string,
onChange: PropTypes.func,
onInput: PropTypes.func,
};
TextArea.defaultProps = {
className: null,
style: null,
value: '',
onChange: null,
onInput: null,
};
export default TextArea;

View File

@ -0,0 +1,58 @@
import './css/Toggle.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 开关
* @author tengge / https://github.com/tengge1
*/
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: props.selected,
};
this.handleChange = this.handleChange.bind(this, props.onChange);
}
handleChange(onChange, event) {
var selected = event.target.classList.contains('selected');
this.setState({
selected: !selected,
});
onChange && onChange(!selected, event);
}
render() {
const { className, style, selected, disabled, onChange, ...others } = this.props;
return <div
className={classNames('Toggle', this.state.selected && 'selected',
disabled && 'disabled',
className)}
style={style}
onClick={disabled ? null : this.handleChange}
{...others}></div>;
}
}
Toggle.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
selected: PropTypes.bool,
disabled: PropTypes.bool,
onChange: PropTypes.func,
};
Toggle.defaultProps = {
className: null,
style: null,
selected: false,
disabled: false,
onChange: null,
};
export default Toggle;

View File

@ -0,0 +1,31 @@
.Button {
height: 24px;
margin: 0 4px;
padding: 0 8px;
color: #fff;
background-color: #e74c3c;
border: none;
display: inline-block;
box-sizing: border-box;
cursor: pointer;
}
.Button.primary {
color: #fff;
background-color: #3399ff;
}
.Button.success {
color: #fff;
background-color: #64cf40;
}
.Button.warn {
color: #fff;
background-color: #f6a623;
}
.Button.disabled {
color: #fff;
background-color: #ebebeb;
}

View File

@ -0,0 +1,21 @@
.CheckBox {
width: 20px;
height: 20px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAvSURBVDhPYxg6oKur6z8lGGoMAoAEyQWjBkLAqIEkgVEDIWCEGkgJhhoz6AEDAwCX46nq5LTHtAAAAABJRU5ErkJggg==);
display: inline-block;
-webkit-appearance: none;
cursor: pointer;
}
.CheckBox.checked {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACYSURBVDhP1YxRCoMwEERzp9ZL1RO156i306QuTJg1jpHFH/vgQXSHl/6Hx7uUKyJD1CgiMkSNIiJD1CgiMkSNznx9+UaG+OGZw6eUedn+Q4b4Y08VM5Eh7UD5XF2yviFD6mGc9uNqPoiZyBB/NPy32YuZyJB24KP+fSQyRI0q6taKDFGjiMgQNYqIDFGjiMgQNYqIzO1J6Qc9ntav8Xl7ewAAAABJRU5ErkJggg==);
}
.CheckBox.disabled {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAvSURBVDhPYxg64PXr1/8pwVBjEAAkSC4YNRACRg0kCYwaCAEj1EBKMNSYQQ8YGACTLe4e+yPbzQAAAABJRU5ErkJggg==);
cursor: default;
}
.CheckBox.checked.disabled {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABkSURBVDhP5dBBCsAgDADB/P+rviCXWFYsLaGNYi6VLgREdARln0opNTOduWJztZ+AZlZVta3TINj9fAr0GC2DTxiF4PkvvjeMQpD8xQijIUjRA74pkNgbYTQNzrYxmJnOfD6RAzbN65BiZNB7AAAAAElFTkSuQmCC);
}

View File

@ -0,0 +1,3 @@
.Form {
box-sizing: border-box;
}

View File

@ -0,0 +1,13 @@
.FormControl {
min-height: 20px;
margin: 4px;
box-sizing: border-box;
}
.FormControl>* {
vertical-align: middle;
}
.FormControl>.Label {
width: 60px;
}

View File

@ -0,0 +1,35 @@
.IconButton {
width: 32px;
height: 32px;
color: #555;
background: none;
margin: 4px;
padding: 0;
border: 1px solid #ccc;
border-radius: 2px;
box-sizing: border-box;
cursor: pointer;
}
.IconButton.selected {
color: #fff;
background: #3399ff;
border: 1px solid #3399ff;
}
.IconButton:hover {
color: #3399ff;
background: none;
border: 1px solid #3399ff;
}
.IconButton.selected:hover {
color: #fff;
background: #3399ff;
border: 2px solid #3399ff;
}
.IconButton .iconfont {
font-size: 20px;
pointer-events: none;
}

View File

@ -0,0 +1,9 @@
.Input {
width: 160px;
font: 12px 'Microsoft YaHei';
margin: 1px 0;
padding: 0 2px;
border: 1px solid rgb(217, 217, 217);
box-sizing: border-box;
vertical-align: top;
}

View File

@ -0,0 +1,6 @@
.Label {
height: 20px;
font: 12px 'Microsoft YaHei';
line-height: 20px;
display: inline-block;
}

View File

@ -0,0 +1,21 @@
.Radio {
width: 20px;
height: 20px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEiSURBVDhP1ZPLaoNAGIVdttC+TUveqHmAEkp3br0gKCq60bfwti40BNF9kodIusrOzIFTxNYxE3d+8MOg/znM/Bdt2RiG8WxZ1krEmrHSdf2Jv+/Ddd3POI4PSZKci6K4IHCOomjved4H09TwfX9XVdWpkyDMz0EQfDN9Gpi1bUupnKZpOpG7pWwcPHPqZn/BTcXzN5QPQQNQM+YqI2p6dBznkTY96CCKzjxloLFt+5U2PRgLdJJ5yuR5fjFN8402PXMNoZEZzn6yqP8LbXqwAaIpe+Ypg6akafpAmyHYgLIslW+ZZdmP0LxTPg42AEN7i7quuzAMvyibBhuAoaX2H7iZstkv2ADUB0VHJxE449vNZ8rABmBoMRYIdFPagIWgaVdKsJshjm46QwAAAABJRU5ErkJggg==);
display: inline-block;
-webkit-appearance: none;
cursor: pointer;
}
.Radio.selected {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAFlSURBVDhP1ZO/SsNQFMYzKuimm4OPoFVxlKoFoTrqI+gDiIhro4KLm9C61ERaseDiE2gRlOqmiLRd62w61enzftcTcluTNHbrDw5cknM+zl9ruJk4wfhcAYupAnZofE+eYUx+/4/0BQ7WS2hsVeDZd+jQ+N4oo77sYF/ckrHqonZcxRciOKzCyzh4FPd4KHbzLpExXL8BGRdPEhYOy+zNrP0NvHz+Gt8m9j28tIM9Ce+GA2DPxFdz+wEsFQE1DG1885tJtozm1ClGRSaAE2TTxU9nY4qZomammypm5hwLIhPAtVAldMQPz62/Yr7xn09OTT+Vx7bIBAwqyJhQQeU4cMmzecyLTAAvgEsrfpqkQ5kuYkRkuuEFHKmlFV8Ns2GJNDMzovrXXnGxK+Hh8AIqamn7cfUKrF3iQcLi4QXYPZmaMLPEYj68gGwJTTadk6TxzZ71LTMKXgCXlmtB4zQjBzAkWNYPuWx6Vj2oQrgAAAAASUVORK5CYII=);
}
.Radio.disabled {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEFSURBVDhP1ZNdCsIwEIT7qGBvo3gjPYCU4v0ExSOoN+ifTUvpW52hI0FJY/TNDxZLsjNsdtfovxmGIc7zfJ1l2ZbBb5wtdP0dEO/Lsrwi6qZpega/i6K44C5VWhgQnWFwRzVOjDE1TI9K90OzrusknYY5yD1J5obP9FX2DitFGxLJX8F9zJ6NqeFAc8PPXDYWTpBNH9PCoQbalWwsXIu2bXvlBcPpQ7uRjeVXQ2qchr8+uaqqGoZL2Vhwt8DlZUwLR0OZyeYVVJmiJ8FVYm0MdnEnuRuYHr9Y7INkfvgP8FWqysLMnqA3CfvDQXGSDA6AZx+fOQWKmXNpuRaKJc7cA/gTougBp8flNLlkjQ0AAAAASUVORK5CYII=);
cursor: default;
}
.Radio.selected.disabled {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAE1SURBVDhP1ZNJDoJAEEVZaiK30XgjPYAhxvuZaDwCegMmGUIIC9r/6UIaRBR3vqRip4ZPdVVr/TdKKdv3/bXneVsaz/AtJDwNFB/CMLzC4jRNCxrPQRC4iO0l7TtQdIHAHd0MkiRJDNGTpI9DsTzPpfQ9zEHuWcqG4TX7nVVVpcqyrI1nE3aKMThS3gVxmzPTqZqiKBQ+orCQ2nimzwQ1N/zMRaYFyWsOXafpzkwxU9TslDXwrUSmBcnbLMuen+cV+2KNMdbA7cO3EZkWOH8SZA18r4K/XjmKohj+pci0ILZA0NVpmglLmYlMFxTsMZNnl4Td8Io0szOCZ5PgLe6kfBiIniY87KOUjcN/QL9TE+nsO7EGzMbhfLgobpLGBdD38ZrvQDNzjGCFhWzElvANL+BPsKwHYaPc6BXkh3gAAAAASUVORK5CYII=);
}

View File

@ -0,0 +1,58 @@
.SearchField {
display: flex;
flex-direction: row;
align-items: center;
}
.SearchField>.input {
width: 0;
font: 12px 'Microsoft YaHei';
margin: 1px 0;
padding: 2px;
border: 1px solid rgb(217, 217, 217);
box-sizing: border-box;
flex: 1;
}
.SearchField>.IconButton {
width: 22px;
height: 22px;
}
.SearchField>.IconButton.hidden {
display: none;
}
.SearchField>.IconButton>.iconfont {
font-size: 14px;
}
.SearchField>.category {
position: absolute;
right: 8px;
top: 28px;
height: 160px;
background: #fff;
border: 1px solid #ccc;
display: inline-block;
z-index: 10;
overflow-y: auto;
}
.SearchField>.category.hidden {
display: none;
}
.SearchField>.category>.item {
padding: 0 8px 0 2px;
}
.SearchField>.category>.item>.CheckBox {
vertical-align: middle;
}
.SearchField>.category>.item>.title {
font-size: 14px;
line-height: 26px;
vertical-align: middle;
}

View File

@ -0,0 +1,3 @@
.Select {
box-sizing: border-box;
}

View File

@ -0,0 +1,10 @@
.TextArea {
width: 160px;
height: 200px;
font: 12px 'Microsoft YaHei';
margin: 0px;
padding: 0px 2px;
border: 1px solid rgb(217, 217, 217);
box-sizing: border-box;
vertical-align: top;
}

View File

@ -0,0 +1,21 @@
.Toggle {
width: 34px;
height: 20px;
margin: 0 4px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAUCAYAAADoZO9yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABYSURBVEhL7da9CQAgDIRR918gC2SBzJGhImlEzsofMMUJrz2+dDYRiQrqhZhZuPsxVV3Gd4yQHLt5ecg8vIshiCGIIYghiCGIIaheSA5lzKln34DfioRIdJ5veQfy/zjLAAAAAElFTkSuQmCC);
display: inline-block;
cursor: pointer;
}
.Toggle.selected {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAUCAYAAADoZO9yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABRSURBVEhLYzCe+f//YMDDxyEzz/z/P4NMXL8fYQ7FDqEEnHmGMGfUISAw6hB0MOoQdDDqEHQw6hB0MOoQdDA8HQJqBpCLqdoMoBYeJA75/x8AV8uDZSB9PMIAAAAASUVORK5CYII=);
}
.Toggle.disabled {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAUCAYAAADoZO9yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABVSURBVEhL7dY7CgAgDERB73/VEEiTOpJGZK38gClWmHZ56WwiEhXUCzGzcPdjqrqM7xghOXbz8pB5eBdDEEMQQxBDEEMQQ1C9kBzKmFPPvgG/FQmR6IJTLfO1daX2AAAAAElFTkSuQmCC);
cursor: default;
}
.Toggle.selected.disabled {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAUCAYAAADoZO9yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABQSURBVEhL7dahCgAgDIRh3/9Vrw1WVhTLwmEQNci4H66Or2kD0H9YHYi7H8/M8s415KaIyDuCzAThBOEE4QThBOFqQlbP++6efgNe7RMI+gDSby3zpGs0DgAAAABJRU5ErkJggg==);
}

View File

@ -0,0 +1,52 @@
import './css/Icon.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 图标
* @author tengge / https://github.com/tengge1
*/
class Icon extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this, props.onClick);
}
render() {
const { className, style, name, icon, title } = this.props;
return <i className={classNames('Icon', 'iconfont',
icon && 'icon-' + icon,
className)}
style={style}
name={name}
title={title}
onClick={this.handleClick}></i>;
}
handleClick(onClick, event) {
const name = event.target.getAttribute('name');
onClick && onClick(name, event);
}
}
Icon.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
name: PropTypes.string,
icon: PropTypes.string,
title: PropTypes.string,
onClick: PropTypes.func,
};
Icon.defaultProps = {
className: null,
style: null,
name: null,
icon: null,
title: null,
onClick: null,
};
export default Icon;

View File

@ -0,0 +1,5 @@
.Icon {
font-size: 20px;
margin: 2px;
display: inline-block;
}

View File

@ -0,0 +1,35 @@
import './css/Image.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 图片
* @author tengge / https://github.com/tengge1
*/
class Image extends React.Component {
render() {
const { className, style, src, title } = this.props;
return <img
className={classNames('Image', className)}
style={style}
src={src}
title={title}></img>;
}
}
Image.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
src: PropTypes.string,
title: PropTypes.string,
};
Image.defaultProps = {
className: null,
style: null,
src: null,
title: null,
};
export default Image;

View File

@ -0,0 +1,167 @@
import './css/ImageList.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
import Icon from '../icon/Icon.jsx';
import IconButton from '../form/IconButton.jsx';
import Input from '../form/Input.jsx';
/**
* 图片列表
* @author tengge / https://github.com/tengge1
*/
class ImageList extends React.Component {
constructor(props) {
super(props);
const { onClick, onEdit, onDelete } = props;
this.state = {
pageSize: 6,
pageNum: 0,
};
this.handleFirstPage = this.handleFirstPage.bind(this);
this.handleLastPage = this.handleLastPage.bind(this);
this.handlePreviousPage = this.handlePreviousPage.bind(this);
this.handleNextPage = this.handleNextPage.bind(this);
this.handleClick = this.handleClick.bind(this, onClick);
this.handleEdit = this.handleEdit.bind(this, onEdit);
this.handleDelete = this.handleDelete.bind(this, onDelete);
}
render() {
const { className, style, data, firstPageText, lastPageText, currentPageText, previousPageText, nextPageText } = this.props;
const { pageSize, pageNum } = this.state;
const totalPage = this.getTotalPage();
const current = data.filter((n, i) => {
return i >= pageSize * pageNum && i < pageSize * (pageNum + 1);
});
return <div className={classNames('ImageList', className)} style={style}>
<div className={'content'}>
{current.map(n => {
return <div className={'item'} data-id={n.id} key={n.id} onClick={this.handleClick}>
{n.src ?
<img className={'img'} src={n.src}></img> :
<div className={'no-img'}>
<Icon icon={n.icon}></Icon>
</div>}
<div className={'title'}>{n.title}</div>
{n.cornerText && <div className={'cornerText'}>{n.cornerText}</div>}
<IconButton className={'edit'} icon={'edit'} data-id={n.id} onClick={this.handleEdit}></IconButton>
<IconButton className={'delete'} icon={'delete'} data-id={n.id} onClick={this.handleDelete}></IconButton>
</div>;
})}
</div>
<div className={'page'}>
<IconButton icon={'backward'} title={firstPageText} onClick={this.handleFirstPage}></IconButton>
<IconButton icon={'left-triangle2'} title={previousPageText} onClick={this.handlePreviousPage}></IconButton>
<Input className={'current'} value={(pageNum + 1).toString()} title={currentPageText} disabled={true} />
<IconButton icon={'right-triangle2'} title={nextPageText} onClick={this.handleNextPage}></IconButton>
<IconButton icon={'forward'} title={lastPageText} onClick={this.handleLastPage}></IconButton>
<div className={'info'}>
<span>{totalPage}</span>
</div>
</div>
</div>;
}
handleFirstPage() {
this.setState({
pageNum: 0,
});
}
handleLastPage() {
const totalPage = this.getTotalPage();
this.setState({
pageNum: totalPage < 1 ? 0 : totalPage - 1,
});
}
handleNextPage() {
this.setState(state => {
const totalPage = this.getTotalPage();
return {
pageNum: state.pageNum < totalPage - 1 ? state.pageNum + 1 : totalPage - 1,
};
});
}
handlePreviousPage() {
this.setState(state => {
return {
pageNum: state.pageNum > 0 ? state.pageNum - 1 : 0,
};
});
}
handleClick(onClick, event) {
event.stopPropagation();
const id = event.target.getAttribute('data-id');
const data = this.props.data.filter(n => n.id === id)[0];
onClick && onClick(data, event);
}
handleEdit(onEdit, event) {
event.stopPropagation();
const id = event.target.getAttribute('data-id');
const data = this.props.data.filter(n => n.id === id)[0];
onEdit && onEdit(data, event);
}
handleDelete(onDelete, event) {
event.stopPropagation();
const id = event.target.getAttribute('data-id');
const data = this.props.data.filter(n => n.id === id)[0];
onDelete && onDelete(data, event);
}
getTotalPage() {
const total = this.props.data.length;
const pageSize = this.state.pageSize;
return total % pageSize === 0 ? total / pageSize : parseInt(total / pageSize) + 1;
}
}
ImageList.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
data: PropTypes.array,
onClick: PropTypes.func,
onEdit: PropTypes.func,
onDelete: PropTypes.func,
firstPageText: PropTypes.string,
lastPageText: PropTypes.string,
currentPageText: PropTypes.string,
previousPageText: PropTypes.string,
nextPageText: PropTypes.string,
};
ImageList.defaultProps = {
className: null,
style: null,
data: [],
onClick: null,
onEdit: null,
onDelete: null,
firstPageText: 'First Page',
lastPageText: 'Last Page',
currentPageText: 'Current Page',
previousPageText: 'Previous Page',
nextPageText: 'Next Page',
};
export default ImageList;

View File

@ -0,0 +1,81 @@
import './css/ImageUploader.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 图片上传控件
* @author tengge / https://github.com/tengge1
*/
class ImageUploader extends React.Component {
constructor(props) {
super(props);
this.handleSelect = this.handleSelect.bind(this);
this.handleChange = this.handleChange.bind(this, props.onChange);
}
render() {
const { className, style, url, server, noImageText } = this.props;
if (url && url != 'null') {
return <img
className={classNames('ImageUploader', className)}
src={server + url}
onClick={this.handleSelect} />;
} else {
return <div
className={classNames('ImageUploader', 'empty', className)}
onClick={this.handleSelect}>
{noImageText}
</div>;
}
}
componentDidMount() {
var input = document.createElement('input');
input.type = 'file';
input.style.display = 'none';
input.addEventListener('change', this.handleChange);
document.body.appendChild(input);
this.input = input;
}
componentWillUnmount() {
var input = this.input;
input.removeEventListener('change', this.handleChange);
document.body.removeChild(input);
this.input = null;
}
handleSelect() {
this.input.click();
}
handleChange(onChange, event) {
onChange && onChange(event.target.files[0], event);
}
}
ImageUploader.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
url: PropTypes.string,
server: PropTypes.string,
noImageText: PropTypes.string,
onChange: PropTypes.func,
};
ImageUploader.defaultProps = {
className: null,
style: null,
url: null,
server: '',
noImageText: 'No Image',
onChange: null,
};
export default ImageUploader;

View File

@ -0,0 +1,133 @@
.ImageList {
position: relative;
width: 100%;
height: calc(100% - 30px);
}
.ImageList>.content {
position: absolute;
width: 100%;
height: calc(100% - 24px);
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: flex-start;
justify-content: flex-start;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: auto;
}
.ImageList>.content>.item {
position: relative;
width: 104px;
height: 104px;
margin: 4px;
display: inline-block;
border: 1px solid #ddd;
box-sizing: border-box;
cursor: pointer;
}
.ImageList>.content>.item>.img {
width: 100%;
height: 100%;
border-radius: 3px;
pointer-events: none;
}
.ImageList>.content>.item>.no-img {
width: 100%;
height: 100px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.ImageList>.content>.item>.title {
position: absolute;
left: 2px;
bottom: 2px;
right: 2px;
background-color: rgba(0, 0, 0, 0.5);
font-size: 12px;
color: #fff;
padding: 1px 4px;
pointer-events: none;
word-break: break-word;
}
.ImageList>.content>.item>.cornerText {
position: absolute;
left: 0;
top: 0;
font-size: 12px;
font-weight: bold;
padding: 0 2px;
color: #555;
background: rgba(255, 255, 255, 0.8);
box-sizing: border-box;
}
.ImageList>.content>.item>.IconButton {
width: 16px;
height: 16px;
margin: 2px;
padding: 0;
background: rgba(255, 255, 255, 0.8);
border: none;
cursor: pointer;
pointer-events: all;
}
.ImageList>.content>.item>.edit {
position: absolute;
right: 24px;
top: 0;
}
.ImageList>.content>.item>.delete {
position: absolute;
right: 2px;
top: 0;
}
.ImageList>.content>.item>.IconButton i {
font-size: 14px;
}
.ImageList>.page {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
font-size: 12px;
padding-top: 3px;
border-top: 1px solid #ddd;
display: flex;
flex-direction: row;
align-items: center;
}
.ImageList>.page>.IconButton {
width: 20px;
height: 20px;
margin: 0 2px;
}
.ImageList>.page>.IconButton>.iconfont {
font-size: 14px;
}
.ImageList>.page>.current {
width: 0;
flex: 1;
margin: 0 2px;
padding: 1px 2px;
}
.ImageList>.page>.info {
margin: 0 2px;
}

View File

@ -0,0 +1,15 @@
.ImageUploader {
max-width: 160px;
max-height: 120px;
border-radius: 2px;
cursor: pointer;
}
.ImageUploader.empty {
width: 160px;
height: 120px;
border: 1px solid #ccc;
display: inline-flex;
align-items: center;
justify-content: center;
}

View File

@ -0,0 +1,88 @@
import './Config.css';
import '../css/icon/iconfont.css';
export { default as classNames } from 'classnames/bind';
export { default as PropTypes } from 'prop-types';
// canvas
export { default as Canvas } from './canvas/Canvas.jsx';
// common
export { default as Accordion } from './common/Accordion.jsx';
export { default as Buttons } from './common/Buttons.jsx';
export { default as Column } from './common/Column.jsx';
export { default as Columns } from './common/Columns.jsx';
export { default as Content } from './common/Content.jsx';
export { default as Row } from './common/Row.jsx';
export { default as Rows } from './common/Rows.jsx';
// form
export { default as Button } from './form/Button.jsx';
export { default as CheckBox } from './form/CheckBox.jsx';
export { default as Form } from './form/Form.jsx';
export { default as FormControl } from './form/FormControl.jsx';
export { default as IconButton } from './form/IconButton.jsx';
export { default as Input } from './form/Input.jsx';
export { default as Label } from './form/Label.jsx';
export { default as Radio } from './form/Radio.jsx';
export { default as SearchField } from './form/SearchField.jsx';
export { default as Select } from './form/Select.jsx';
export { default as TextArea } from './form/TextArea.jsx';
export { default as Toggle } from './form/Toggle.jsx';
// icon
export { default as Icon } from './icon/Icon.jsx';
// image
export { default as Image } from './image/Image.jsx';
export { default as ImageList } from './image/ImageList.jsx';
export { default as ImageUploader } from './image/ImageUploader.jsx';
// layout
export { default as AbsoluteLayout } from './layout/AbsoluteLayout.jsx';
export { default as AccordionLayout } from './layout/AccordionLayout.jsx';
export { default as BorderLayout } from './layout/BorderLayout.jsx';
export { default as HBoxLayout } from './layout/HBoxLayout.jsx';
export { default as TabLayout } from './layout/TabLayout.jsx';
export { default as VBoxLayout } from './layout/VBoxLayout.jsx';
// menu
export { default as MenuBar } from './menu/MenuBar.jsx';
export { default as MenuBarFiller } from './menu/MenuBarFiller.jsx';
export { default as MenuItem } from './menu/MenuItem.jsx';
export { default as MenuItemSeparator } from './menu/MenuItemSeparator.jsx';
// panel
export { default as Panel } from './panel/Panel.jsx';
// property
export { default as PropertyGrid } from './property/PropertyGrid.jsx';
// svg
export { default as SVG } from './svg/SVG.jsx';
// table
export { default as DataGrid } from './table/DataGrid.jsx';
export { default as Table } from './table/Table.jsx';
export { default as TableBody } from './table/TableBody.jsx';
export { default as TableCell } from './table/TableCell.jsx';
export { default as TableHead } from './table/TableHead.jsx';
export { default as TableRow } from './table/TableRow.jsx';
// timeline
export { default as Timeline } from './timeline/Timeline.jsx';
// toolbar
export { default as Toolbar } from './toolbar/Toolbar.jsx';
export { default as ToolbarFiller } from './toolbar/ToolbarFiller.jsx';
export { default as ToolbarSeparator } from './toolbar/ToolbarSeparator.jsx';
// tree
export { default as Tree } from './tree/Tree.jsx';
// window
export { default as Alert } from './window/Alert.jsx';
export { default as Confirm } from './window/Confirm.jsx';
export { default as Prompt } from './window/Prompt.jsx';
export { default as Toast } from './window/Toast.jsx';
export { default as Window } from './window/Window.jsx';

View File

@ -0,0 +1,41 @@
import './css/AbsoluteLayout.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 绝对定位布局
* @author tengge / https://github.com/tengge1
*/
class AbsoluteLayout extends React.Component {
render() {
const { className, style, children, left, top, ...others } = this.props;
const position = {
left: left || 0,
top: top || 0,
};
return <div
className={classNames('AbsoluteLayout', className)}
style={style ? Object.assign({}, style, position) : position}
{...others}>{children}</div>;
}
}
AbsoluteLayout.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
left: PropTypes.string,
top: PropTypes.string,
};
AbsoluteLayout.defaultProps = {
className: null,
style: null,
children: null,
left: '0',
top: '0',
};
export default AbsoluteLayout;

View File

@ -0,0 +1,67 @@
import './css/AccordionLayout.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
import AccordionPanel from './private/AccordionPanel.jsx';
/**
* 折叠布局
* @author tengge / https://github.com/tengge1
*/
class AccordionLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
activeIndex: props.activeIndex,
};
this.handleClick = this.handleClick.bind(this, props.onActive);
}
handleClick(onActive, index, name, event) {
onActive && onActive(index, name, event);
this.setState({
activeIndex: index,
});
}
render() {
const { className, style, children } = this.props;
const content = Array.isArray(children) ? children : [children];
return <div className={classNames('AccordionLayout', className)} style={style}>
{content.map((n, i) => {
return <AccordionPanel
name={n.props.name}
title={n.props.title}
show={n.props.show}
total={content.length}
index={i}
collpased={i !== this.state.activeIndex}
maximizable={n.props.maximizable}
onClick={this.handleClick}
key={i}>{n.props.children}</AccordionPanel>;
})}
</div>;
}
}
AccordionLayout.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
activeIndex: PropTypes.number,
onActive: PropTypes.func,
};
AccordionLayout.defaultProps = {
className: null,
style: null,
children: null,
activeIndex: 0,
onActive: null,
};
export default AccordionLayout;

View File

@ -0,0 +1,291 @@
import './css/BorderLayout.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 边框布局
* @author tengge / https://github.com/tengge1
*/
class BorderLayout extends React.Component {
constructor(props) {
super(props);
const children = this.props.children;
const north = children && children.filter(n => n.props.region === 'north')[0];
const south = children && children.filter(n => n.props.region === 'south')[0];
const west = children && children.filter(n => n.props.region === 'west')[0];
const east = children && children.filter(n => n.props.region === 'east')[0];
const center = children && children.filter(n => n.props.region === 'center')[0];
const northSplit = north && north.props.split || false;
const southSplit = south && south.props.split || false;
const westSplit = west && west.props.split || false;
const eastSplit = east && east.props.split || false;
const northCollapsed = north && north.props.collapsed || false;
const southCollapsed = south && south.props.collapsed || false;
const westCollapsed = west && west.props.collapsed || false;
const eastCollapsed = east && east.props.collapsed || false;
const onNorthToggle = north && north.props.onToggle || null;
const onSouthToggle = south && south.props.onToggle || null;
const onWestToggle = west && west.props.onToggle || null;
const onEastToggle = east && east.props.onToggle || null;
this.northRef = React.createRef();
this.southRef = React.createRef();
this.westRef = React.createRef();
this.eastRef = React.createRef();
this.state = {
northSplit, southSplit, westSplit, eastSplit,
northCollapsed, southCollapsed, westCollapsed, eastCollapsed,
};
this.handleNorthClick = this.handleNorthClick.bind(this, onNorthToggle);
this.handleSouthClick = this.handleSouthClick.bind(this, onSouthToggle);
this.handleWestClick = this.handleWestClick.bind(this, onWestToggle);
this.handleEastClick = this.handleEastClick.bind(this, onEastToggle);
this.handleTransitionEnd = this.handleTransitionEnd.bind(this, onNorthToggle, onSouthToggle, onWestToggle, onEastToggle);
}
handleNorthClick() {
if (!this.state.northSplit) {
return;
}
this.setState((state, props) => {
const collapsed = !state.northCollapsed;
const dom = this.northRef.current;
const height = dom.clientHeight;
if (collapsed) {
dom.style.marginTop = `-${height - 8}px`;
} else {
dom.style.marginTop = null;
}
return {
northCollapsed: collapsed,
};
});
}
handleSouthClick() {
if (!this.state.southSplit) {
return;
}
this.setState((state, props) => {
const collapsed = !state.southCollapsed;
const dom = this.southRef.current;
const height = dom.clientHeight;
if (collapsed) {
dom.style.marginBottom = `-${height - 8}px`;
} else {
dom.style.marginBottom = null;
}
return {
southCollapsed: collapsed,
};
});
}
handleWestClick() {
if (!this.state.westSplit) {
return;
}
const dom = this.westRef.current;
this.setState((state, props) => {
const collapsed = !state.westCollapsed;
const width = dom.clientWidth;
if (collapsed) {
dom.style.marginLeft = `-${width - 8}px`;
} else {
dom.style.marginLeft = null;
}
return {
westCollapsed: collapsed,
};
});
}
handleEastClick() {
if (!this.state.eastSplit) {
return;
}
this.setState((state, props) => {
const collapsed = !state.eastCollapsed;
const dom = this.eastRef.current;
const width = dom.clientWidth;
if (collapsed) {
dom.style.marginRight = `-${width - 8}px`;
} else {
dom.style.marginRight = null;
}
return {
eastCollapsed: collapsed,
};
});
}
handleTransitionEnd(onNorthToggle, onSouthToggle, onWestToggle, onEastToggle, event) {
const region = event.target.getAttribute('region');
switch (region) {
case 'north':
onNorthToggle && onNorthToggle(!this.state.northCollapsed);
break;
case 'south':
onSouthToggle && onSouthToggle(!this.state.southCollapsed);
break;
case 'west':
onWestToggle && onWestToggle(!this.state.westCollapsed);
break;
case 'east':
onEastToggle && onEastToggle(!this.state.eastCollapsed);
break;
}
}
render() {
const { className, style, children } = this.props;
let north = [], south = [], west = [], east = [], center = [], others = [];
children && children.forEach(n => {
switch (n.props.region) {
case 'north':
north.push(n);
break;
case 'south':
south.push(n);
break;
case 'west':
west.push(n);
break;
case 'east':
east.push(n);
break;
case 'center':
center.push(n);
break;
default:
others.push(n);
break;
}
});
if (center.length === 0) {
console.warn(`BorderLayout: center region is not defined.`);
}
// north region
const northRegion = north.length > 0 && (<div className={classNames('north',
this.state.northSplit && 'split',
this.state.northCollapsed && 'collapsed')}
region={'north'}
onTransitionEnd={this.handleTransitionEnd}
ref={this.northRef}>
<div className={'content'}>
{north}
</div>
{this.state.northSplit && <div className={'control'}>
<div className={'button'} onClick={this.handleNorthClick}></div>
</div>}
</div>);
// south region
const southRegion = south.length > 0 && (<div className={classNames('south',
this.state.northSplit && 'split',
this.state.southCollapsed && 'collapsed')}
region={'south'}
onTransitionEnd={this.handleTransitionEnd}
ref={this.southRef}>
{this.state.southSplit && <div className={'control'}>
<div className={'button'} onClick={this.handleSouthClick}></div>
</div>}
<div className={'content'}>
{south}
</div>
</div>);
// west region
const westRegion = west.length > 0 && (<div className={classNames('west',
this.state.westSplit && 'split',
this.state.westCollapsed && 'collapsed')}
region={'west'}
onTransitionEnd={this.handleTransitionEnd}
ref={this.westRef}>
<div className={'content'}>
{west}
</div>
{this.state.westSplit && <div className={'control'}>
<div className={'button'} onClick={this.handleWestClick}></div>
</div>}
</div>);
// east region
const eastRegion = east.length > 0 && (<div className={classNames('east',
this.state.eastSplit && 'split',
this.state.eastCollapsed && 'collapsed')}
region={'east'}
onTransitionEnd={this.handleTransitionEnd}
ref={this.eastRef}>
<div className={'control'}>
<div className={'button'} onClick={this.handleEastClick}></div>
</div>
<div className={'content'}>
{east}
</div>
</div>);
// center region
const centerRegion = center.length > 0 && (<div className={'center'}>
{center}
</div>);
const otherRegion = others.length > 0 && others;
return <div
className={classNames('BorderLayout', className)}
style={style}>
{northRegion}
<div className={'middle'}>
{westRegion}
{centerRegion}
{eastRegion}
</div>
{southRegion}
{otherRegion}
</div>;
}
}
BorderLayout.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
BorderLayout.defaultProps = {
className: null,
style: null,
children: null,
};
export default BorderLayout;

View File

@ -0,0 +1,32 @@
import './css/HBoxLayout.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 水平布局
* @author tengge / https://github.com/tengge1
*/
class HBoxLayout extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <div
className={classNames('HBoxLayout', className)}
style={style}
{...others}>{children}</div>;
}
}
HBoxLayout.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
HBoxLayout.defaultProps = {
className: null,
style: null,
children: null,
};
export default HBoxLayout;

View File

@ -0,0 +1,64 @@
import './css/TabLayout.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 选项卡布局
* @author tengge / https://github.com/tengge1
*/
class TabLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
activeTab: props.activeTab,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
var tabIndex = event.target.tabIndex;
this.setState({
activeTab: tabIndex,
});
}
render() {
const { className, style, children, activeTab, ...others } = this.props;
return <div className={classNames('TabLayout', className)} style={style}>
<div className={'tabs'} {...others}>
{children.map((n, i) => {
return <div
className={classNames('tab', i === this.state.activeTab ? 'selected' : null)}
key={i}
tabIndex={i}
onClick={this.handleClick}
>{n.props.title}</div>;
})}
</div>
<div className={'contents'}>
{children.map((n, i) => {
return <div className={classNames('content', i === this.state.activeTab ? 'show' : null)} key={i}>{n}</div>;
})}
</div>
</div>;
}
}
TabLayout.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
activeTab: PropTypes.number,
};
TabLayout.defaultProps = {
className: null,
style: null,
children: null,
activeTab: 0,
};
export default TabLayout;

View File

@ -0,0 +1,32 @@
import './css/VBoxLayout.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 竖直布局
* @author tengge / https://github.com/tengge1
*/
class VBoxLayout extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <div
className={classNames('VBoxLayout', className)}
style={style}
{...others}>{children}</div>;
}
}
VBoxLayout.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
VBoxLayout.defaultProps = {
className: null,
style: null,
children: null,
};
export default VBoxLayout;

View File

@ -0,0 +1,4 @@
.AbsoluteLayout {
position: absolute;
display: block;
}

View File

@ -0,0 +1,5 @@
.AccordionLayout {
width: 100%;
height: 100%;
display: block;
}

View File

@ -0,0 +1,198 @@
.BorderLayout {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
--up-arrow: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAJCAMAAAB30J7MAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODU5RUQ4NzY3RTI2MTFFOUE4RjBBODU0OThFNTczRkMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODU5RUQ4NzU3RTI2MTFFOUE4RjBBODU0OThFNTczRkMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmRpZDo3ODlBMTI3RDI2N0VFOTExOTFBREVBQjM5NUM3ODkwMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODlBMTI3RDI2N0VFOTExOTFBREVBQjM5NUM3ODkwMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PlbUzZ8AAAMAUExURfb29tra2tnZ2fX19YuLi4qKivf39wcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///9oQUYcAAAA3SURBVHjaYvhOJGCgRGFX1zeiFHYB0TciFHaBiW8EFXZBqa8EFHbBGN3f8Cq8hWDeJDN4AAIMAEFsVuZN2iPeAAAAAElFTkSuQmCC);
--down-arrow: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAJCAMAAAB30J7MAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OEZDOUYxQTk3RTI2MTFFOTlBRTE4RTE1RkRCMUI3MEYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OEZDOUYxQTg3RTI2MTFFOTlBRTE4RTE1RkRCMUI3MEYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmRpZDo3ODlBMTI3RDI2N0VFOTExOTFBREVBQjM5NUM3ODkwMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODlBMTI3RDI2N0VFOTExOTFBREVBQjM5NUM3ODkwMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqOucMsAAAMAUExURfb29tra2tnZ2fX19YuLi4qKivf39wcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///9oQUYcAAAA/SURBVHjaYvhOJGBA5d5EMG/hVfitG8bqwm/i969d2NVhKPz+rQurOkyFYJVd34lQCFTZ9Z0ohUQFD24AEGAACN5W5nESKn0AAAAASUVORK5CYII=);
--left-arrow: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAoCAMAAAAbvyCxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REE4QkI1QUU3OTZEMTFFOTk0NzNEQjBGRjI0QzU2NzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REE4QkI1QUY3OTZEMTFFOTk0NzNEQjBGRjI0QzU2NzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEQThCQjVBQzc5NkQxMUU5OTQ3M0RCMEZGMjRDNTY3NCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEQThCQjVBRDc5NkQxMUU5OTQ3M0RCMEZGMjRDNTY3NCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuJET8oAAAAVUExURfb29tra2tnZ2fX19YuLi4qKivf39wWvvrYAAAA7SURBVHjaYmCDAYbBwmJggrKYWaBiDKxQWTADxIIwQCxWNkwWGyvCPFaEyawIO1gR9jIOHp8DAUCAAQBxQwhCpBDaBAAAAABJRU5ErkJggg==);
--right-arrow: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAoCAMAAAAbvyCxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MEI0QUI4OEY3OTZFMTFFOTlGQjFGRTU4QTJGRTA0QTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MEI0QUI4OTA3OTZFMTFFOTlGQjFGRTU4QTJGRTA0QTgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowQjRBQjg4RDc5NkUxMUU5OUZCMUZFNThBMkZFMDRBOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowQjRBQjg4RTc5NkUxMUU5OUZCMUZFNThBMkZFMDRBOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtHSBrsAAAAVUExURfb29tnZ2YuLi/X19djY2IqKivf3956cLrcAAAA3SURBVHjazI/JDQAwDMLcg+4/cl8WK4SXFQISPMUAur2lbvqXJtJsJCR0MXFsYdu8Bi1XX4ABAHgwCELn0SAjAAAAAElFTkSuQmCC);
}
/* north */
.BorderLayout>.north {
border-bottom: 1px solid #eee;
display: flex;
flex-direction: column;
align-items: stretch;
z-index: 100;
transition: all 0.4s;
}
.BorderLayout>.north>.content {
width: 100%;
height: 100%;
display: block;
}
.BorderLayout>.north.split>.content {
overflow-y: hidden;
}
.BorderLayout>.north>.control {
height: 8px;
border-top: 1px solid #eee;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.BorderLayout>.north>.control>.button {
width: 40px;
height: 9px;
background: var(--up-arrow);
cursor: pointer;
}
.BorderLayout>.north.collapsed>.control>.button {
background: var(--down-arrow);
}
/* south */
.BorderLayout>.south {
border-top: 1px solid #eee;
display: flex;
flex-direction: column;
align-items: stretch;
z-index: 100;
transition: all 0.4s;
}
.BorderLayout>.south>.content {
width: 100%;
height: 100%;
display: block;
}
.BorderLayout>.south.split>.content {
overflow-y: hidden;
}
.BorderLayout>.south>.control {
height: 8px;
border-bottom: 1px solid #eee;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.BorderLayout>.south>.control>.button {
width: 40px;
height: 9px;
cursor: pointer;
}
.BorderLayout>.south>.control>.button {
background: var(--down-arrow);
}
.BorderLayout>.south.collapsed>.control>.button {
background: var(--up-arrow);
}
/* middle */
.BorderLayout>.middle {
flex: 1;
display: flex;
flex-direction: row;
overflow: hidden;
}
/* west */
.BorderLayout>.middle>.west {
border-right: 1px solid #eee;
display: flex;
flex-direction: row;
align-items: stretch;
transition: all 0.4s;
}
.BorderLayout>.middle>.west>.content {
width: 100%;
height: 100%;
display: block;
}
.BorderLayout>.middle>.west.split>.content {
overflow-x: hidden;
}
.BorderLayout>.middle>.west>.control {
width: 8px;
border-left: 1px solid #eee;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.BorderLayout>.middle>.west>.control>.button {
width: 9px;
height: 40px;
cursor: pointer;
}
.BorderLayout>.middle>.west>.control>.button {
background: var(--left-arrow);
}
.BorderLayout>.middle>.west.collapsed>.control>.button {
background: var(--right-arrow);
}
/* center */
.BorderLayout>.middle>.center {
position: relative;
flex: 1;
display: block;
}
/* east */
.BorderLayout>.middle>.east {
border-left: 1px solid #eee;
display: flex;
flex-direction: row;
align-items: stretch;
transition: all 0.4s;
}
.BorderLayout>.middle>.east>.content {
width: 100%;
height: 100%;
display: block;
}
.BorderLayout>.middle>.east.split>.content {
overflow-x: hidden;
}
.BorderLayout>.middle>.east>.control {
width: 8px;
border-right: 1px solid #eee;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.BorderLayout>.middle>.east>.control>.button {
width: 9px;
height: 40px;
cursor: pointer;
}
.BorderLayout>.middle>.east>.control>.button {
background: var(--right-arrow);
}
.BorderLayout>.middle>.east.collapsed>.control>.button {
background: var(--left-arrow);
}

View File

@ -0,0 +1,3 @@
.Content {
display: block-inline;
}

View File

@ -0,0 +1,6 @@
.HBoxLayout {
display: flex;
flex-direction: row;
overflow-x: auto;
overflow-y: hidden;
}

View File

@ -0,0 +1,46 @@
.TabLayout {
position: relative;
}
.TabLayout>.tabs {
height: 32px;
box-sizing: border-box;
}
.TabLayout>.tabs>.tab {
height: 32px;
font: 12px 'Microsoft YaHei';
line-height: 32px;
padding: 0 8px;
color: #737373;
display: inline-block;
box-sizing: border-box;
cursor: pointer;
}
.TabLayout>.tabs>.tab.selected {
color: #3399ff;
border-bottom: 1px solid #3399ff;
cursor: default;
}
.TabLayout>.contents {
position: relative;
width: 100%;
height: calc(100% - 32px);
}
.TabLayout>.contents>.content {
position: relative;
width: 100%;
height: 100%;
display: none;
}
.TabLayout>.contents>.content.show {
display: block;
}
.TabLayout>.contents>.content>* {
height: 100%;
}

View File

@ -0,0 +1,6 @@
.VBoxLayout {
display: flex;
flex-direction: column;
overflow-x: hidden;
overflow-y: auto;
}

View File

@ -0,0 +1,96 @@
import './css/AccordionPanel.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 单个折叠面板
* @private
* @author tengge / https://github.com/tengge1
*/
class AccordionPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
maximized: props.maximized,
};
this.handleClick = this.handleClick.bind(this, props.onClick, props.index, props.name);
this.handleMaximize = this.handleMaximize.bind(this, props.onMaximize);
}
handleClick(onClick, index, name, event) {
onClick && onClick(index, name, event);
}
handleMaximize(onMaximize, event) {
this.setState(state => ({
maximized: !state.maximized,
}));
onMaximize && onMaximize(event);
}
render() {
const { title, className, style, children, show, total, index, collpased,
maximizable, maximized, onMaximize } = this.props;
const maximizeControl = maximizable && <div className={'control'} onClick={this.handleMaximize}>
{this.state.maximized ? <i className={'iconfont icon-minimize'}></i> : <i className={'iconfont icon-maximize'}></i>}
</div>;
const _style = collpased ? style : Object.assign({}, style, {
height: `calc(100% - ${26 * (total - 1)}px`,
});
return <div className={classNames('AccordionPanel',
this.state.maximized && 'maximized',
collpased && 'collpased',
!show && 'hidden',
className)} style={_style}>
<div className={'header'} onClick={this.handleClick}>
<span className="title">{title}</span>
<div className="controls">
{maximizeControl}
</div>
</div>
<div className={'body'}>
{children}
</div>
</div>;
}
}
AccordionPanel.propTypes = {
name: PropTypes.string,
title: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
show: PropTypes.bool,
total: PropTypes.number,
index: PropTypes.number,
collpased: PropTypes.bool,
maximizable: PropTypes.bool,
maximized: PropTypes.bool,
onMaximize: PropTypes.bool,
onClick: PropTypes.func,
};
AccordionPanel.defaultProps = {
name: null,
title: null,
className: null,
style: null,
children: null,
show: true,
total: 1,
index: 0,
collpased: true,
maximizable: false,
maximized: false,
onMaximize: null,
onClick: null,
};
export default AccordionPanel;

View File

@ -0,0 +1,82 @@
.AccordionPanel {
position: relative;
left: 0;
top: 0;
width: 240px;
height: 320px;
background: #fafafa;
box-sizing: border-box;
transition: all 0.4s;
}
.AccordionPanel.collpased {
height: 26px;
overflow: hidden;
}
.AccordionPanel.maximized {
position: fixed;
left: 0;
top: 0;
width: 100% !important;
height: 100% !important;
z-index: 9000;
}
.AccordionPanel.hidden {
display: none;
}
.AccordionPanel>.header {
position: relative;
height: 24px;
background-color: #2c3e50;
cursor: pointer;
}
.AccordionPanel>.header>.title {
height: 100%;
line-height: 24px;
font-size: 12px;
padding-left: 8px;
color: #fff;
display: inline-block;
box-sizing: border-box;
}
.AccordionPanel>.header>.controls {
position: absolute;
left: 0;
right: 4px;
top: 0;
bottom: 0;
text-align: right;
white-space: nowrap;
}
.AccordionPanel>.header>.controls>.control {
width: 24px;
height: 24px;
margin: 0 2px;
display: inline-block;
box-sizing: border-box;
cursor: pointer;
}
.AccordionPanel>.header>.controls>.control>.iconfont {
font-size: 16px;
line-height: 24px;
color: #fff;
}
.AccordionPanel>.body {
height: calc(100% - 24px);
padding: 4px;
box-sizing: border-box;
}
.AccordionPanel.collapsed>.body {
height: 0;
padding: 0;
overflow-y: hidden;
}

View File

@ -0,0 +1,32 @@
import './css/MenuBar.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 菜单栏
* @author tengge / https://github.com/tengge1
*/
class MenuBar extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <ul
className={classNames('MenuBar', className)}
style={style}
{...others}>{children}</ul>;
}
}
MenuBar.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
MenuBar.defaultProps = {
className: null,
style: null,
children: null,
};
export default MenuBar;

View File

@ -0,0 +1,27 @@
import './css/MenuBarFiller.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 菜单栏填充
* @author tengge / https://github.com/tengge1
*/
class MenuBarFiller extends React.Component {
render() {
const { className, style } = this.props;
return <li className={classNames('MenuItem', 'MenuBarFiller', className)} style={style}></li>;
}
}
MenuBarFiller.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
};
MenuBarFiller.defaultProps = {
className: null,
style: null,
};
export default MenuBarFiller;

View File

@ -0,0 +1,59 @@
import './css/MenuItem.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 菜单项
* @author tengge / https://github.com/tengge1
*/
class MenuItem extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this, props.onClick);
}
handleClick(onClick, event) {
event.stopPropagation();
onClick && onClick(event);
}
render() {
const { title, className, style, children, show, onClick, ...others } = this.props;
const subMenu = children && children.length && <><div className={'suffix'}>
<i className={'iconfont icon-right-triangle'}></i>
</div>
<div className={'sub'}>
<ul className={'wrap'}>{children}</ul>
</div></>;
return <li
className={classNames('MenuItem', !show && 'hidden', className)}
style={style}
onClick={this.handleClick}
{...others}>
<span>{title}</span>
{subMenu}
</li>;
}
}
MenuItem.propTypes = {
title: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
show: PropTypes.bool,
onClick: PropTypes.func,
};
MenuItem.defaultProps = {
title: null,
className: null,
style: null,
children: null,
show: true,
onClick: null,
};
export default MenuItem;

View File

@ -0,0 +1,32 @@
import './css/MenuItemSeparator.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 菜单项分隔符
* @author tengge / https://github.com/tengge1
*/
class MenuItemSeparator extends React.Component {
render() {
const { className, style, ...others } = this.props;
return <li
className={classNames('MenuItemSeparator', className)}
style={style}
{...others}>
<div className='separator'></div>
</li>;
}
}
MenuItemSeparator.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
};
MenuItemSeparator.defaultProps = {
className: null,
style: null,
};
export default MenuItemSeparator;

View File

@ -0,0 +1,38 @@
.MenuBar {
font: 13px 'Microsoft YaHei';
color: rgb(0, 0, 0);
margin: 0;
padding: 0;
background: linear-gradient(to bottom, rgb(250, 252, 253), rgb(232, 241, 251) 40%, rgb(220, 230, 243) 40%, rgb(220, 231, 245));
border-bottom: 1px solid #bbb;
display: flex;
box-sizing: border-box;
list-style: none;
}
.MenuBar>.MenuItem {
position: relative;
min-width: auto;
margin: 0;
padding: 0 16px;
display: inline-block;
cursor: default;
}
.MenuBar>.MenuItem:hover {
background: initial;
color: initial;
margin: 0;
padding: 0 16px;
border: initial;
}
.MenuBar>.MenuItem>.suffix {
display: none;
}
.MenuBar>.MenuItem>.sub {
position: absolute;
left: 0;
top: auto;
}

View File

@ -0,0 +1,3 @@
.MenuBarFiller {
flex: 1;
}

View File

@ -0,0 +1,69 @@
.MenuItem {
position: relative;
min-width: 120px;
line-height: 24px;
margin: 3px;
padding: 2px 14px 2px 30px;
vertical-align: top;
white-space: nowrap;
box-sizing: border-box;
cursor: pointer;
user-select: none;
--right-arrow: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABAQMAAADO7O3JAAAAA3NCSVQICAjb4U/gAAAABlBMVEXi4+P///9V63aNAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M1cbXjNgAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8wNi8xMZANh+UAAAAKSURBVAiZY3AAAABCAEGV6TQ4AAAAAElFTkSuQmCC') repeat-y rgb(240, 240, 240);
}
.MenuItem:hover {
background: linear-gradient(to bottom, rgba(193, 222, 255, 0.2), rgba(193, 222, 255, 0.4));
color: black;
padding: 1px 13px 1px 29px;
border: 1px solid rgb(183, 212, 246);
}
.MenuItem>span {
pointer-events: none;
}
.MenuItem>.suffix {
width: 40px;
margin-left: 48px;
margin-right: -4px;
text-align: right;
display: inline-block;
pointer-events: none;
}
.MenuItem>.sub {
position: absolute;
left: calc(100% + 4px);
top: -4px;
box-sizing: border-box;
cursor: default;
display: none;
z-index: 300;
}
.MenuItem>.sub::before {
position: absolute;
left: -6px;
height: 100%;
width: 8px;
content: '';
}
.MenuItem:hover>.sub {
display: block;
}
.MenuItem>.sub>.wrap {
position: relative;
list-style: none;
background: var(--right-arrow);
background-position: 24px 0;
margin: 0;
padding: 0;
border: 1px solid rgb(195, 195, 195);
border-radius: 5px;
box-shadow: rgba(128, 128, 128, 0.5) 0px 0px 16px 1px;
box-sizing: border-box;
vertical-align: text-bottom;
}

View File

@ -0,0 +1,11 @@
.MenuItemSeparator {
white-space: nowrap;
display: block;
}
.MenuItemSeparator .separator {
height: 2px;
margin-left: 24px;
background: rgb(229, 229, 229);
background-image: linear-gradient(to bottom, rgb(226, 226, 226), rgb(226, 226, 226) 50%, rgb(252, 252, 252) 50%, rgb(252, 252, 252));
}

View File

@ -0,0 +1,123 @@
import './css/Panel.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 面板
* @author tengge / https://github.com/tengge1
*/
class Panel extends React.Component {
constructor(props) {
super(props);
this.state = {
collapsed: props.collapsed,
maximized: props.maximized,
closed: props.closed,
};
this.handleCollapse = this.handleCollapse.bind(this, props.onCollapse);
this.handleMaximize = this.handleMaximize.bind(this, props.onMaximize);
this.handleClose = this.handleClose.bind(this, props.onClose);
}
handleCollapse(onCollapse, event) {
this.setState(state => ({
collapsed: !state.collapsed,
}));
onCollapse && onCollapse(event);
}
handleMaximize(onMaximize, event) {
this.setState(state => ({
maximized: !state.maximized,
}));
onMaximize && onMaximize(event);
}
handleClose(onClose, event) {
this.setState(state => ({
closed: !state.closed,
}));
onClose && onClose(event);
}
render() {
const { title, className, style, children, show, header,
collapsible, collapsed, onCollapse,
maximizable, maximized, onMaximize,
closable, closed, onClose } = this.props;
const collapseControl = collapsible && <div className={'control'} onClick={this.handleCollapse}>
{this.state.collapsed ? <i className={'iconfont icon-down-arrow'}></i> : <i className={'iconfont icon-up-arrow'}></i>}
</div>;
const maximizeControl = maximizable && <div className={'control'} onClick={this.handleMaximize}>
{this.state.maximized ? <i className={'iconfont icon-minimize'}></i> : <i className={'iconfont icon-maximize'}></i>}
</div>;
const closeControl = closable && <div className={'control'} onClick={this.handleClose}>
<i className={'iconfont icon-close-thin'}></i>
</div>;
return <div className={classNames('Panel',
this.state.maximized && 'maximized',
this.state.collapsed && 'collapsed',
this.state.closed && 'hidden',
!show && 'hidden',
className)} style={style}>
<div className={classNames('header', header ? null : 'hidden')}>
<span className="title">{title}</span>
<div className="controls">
{collapseControl}
{maximizeControl}
{closeControl}
</div>
</div>
<div className={'body'}>
{children}
</div>
</div>;
}
}
Panel.propTypes = {
title: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
show: PropTypes.bool,
header: PropTypes.bool,
collapsible: PropTypes.bool,
collapsed: PropTypes.bool,
onCollapse: PropTypes.func,
maximizable: PropTypes.bool,
maximized: PropTypes.bool,
onMaximize: PropTypes.bool,
closable: PropTypes.bool,
closed: PropTypes.bool,
onClose: PropTypes.func,
};
Panel.defaultProps = {
title: null,
className: null,
style: null,
children: null,
show: true,
header: true,
collapsible: false,
collapsed: false,
onCollapse: null,
maximizable: false,
maximized: false,
onMaximize: null,
closable: false,
closed: false,
onClose: null,
};
export default Panel;

View File

@ -0,0 +1,88 @@
.Panel {
position: relative;
left: 0;
top: 0;
width: 200px;
height: 320px;
background: #fafafa;
border: 1px solid #2c3e50;
box-sizing: border-box;
}
.Panel.maximized {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 9000;
}
.Panel.collapsed {
height: auto;
}
.Panel.hidden {
display: none;
}
.Panel>.header {
position: relative;
height: 24px;
background-color: #2c3e50;
}
.Panel>.header.hidden {
display: none;
}
.Panel>.header>.title {
height: 100%;
line-height: 24px;
font-size: 12px;
padding-left: 8px;
color: #fff;
display: inline-block;
box-sizing: border-box;
}
.Panel>.header>.controls {
position: absolute;
left: 0;
right: 4px;
top: 0;
bottom: 0;
text-align: right;
white-space: nowrap;
}
.Panel>.header>.controls>.control {
width: 32px;
height: 32px;
margin: 0 4px;
display: inline-block;
box-sizing: border-box;
cursor: pointer;
}
.Panel>.header>.controls>.control>.iconfont {
font-size: 16px;
line-height: 24px;
color: #fff;
}
.Panel>.body {
height: calc(100% - 24px);
padding: 4px;
box-sizing: border-box;
}
.Panel.collapsed>.body {
height: 0;
padding: 0;
overflow-y: hidden;
}
.Panel>.header.hidden+div.body {
height: 100%;
}

View File

@ -0,0 +1,60 @@
import './css/PropertyGrid.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 属性表格
* @author tengge / https://github.com/tengge1
*/
class PropertyGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
handleCollapse() {
}
render() {
const { className, style, data } = this.props;
return <div className={classNames('PropertyGrid', className)} style={style}>
{data.map((group, i) => {
return <div className={'group'} key={i}>
<div className={'head'}>
<div className={'icon'}>
<i className={group.expand !== false ? 'icon-expand' : 'icon-collapse'} />
</div>
<div className={'title'}>{group.name}</div>
</div>
<div className={classNames('property', group.expand === false ? 'hide' : null)}>
{group.children.map((item, j) => {
return <div className={'item'} key={item.name || j}>
<div className={'label'}>{item.label}</div>
<div className={'value'}>{item.value}</div>
</div>;
})}
</div>
</div>;
})}
</div>;
}
}
PropertyGrid.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
data: PropTypes.array,
};
PropertyGrid.defaultProps = {
className: null,
style: null,
data: [],
};
export default PropertyGrid;

View File

@ -0,0 +1,84 @@
.PropertyGrid {
position: relative;
color: black;
font: 12px 'Microsoft YaHei';
box-sizing: border-box;
overflow-x: hidden;
overflow-y: auto;
cursor: default;
user-select: none;
--bg-expand: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAMAAAD3JJ6EAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzBCN0FCRUU3NzExMTFFOTlFQjFFMDA4RkM3NzE5OTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzBCN0FCRUY3NzExMTFFOTlFQjFFMDA4RkM3NzE5OTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMEI3QUJFQzc3MTExMUU5OUVCMUUwMDhGQzc3MTk5MiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDMEI3QUJFRDc3MTExMUU5OUVCMUUwMDhGQzc3MTk5MiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrAM5dcAAABFUExURb/BwfT3977BwamqqsnMzZOUlNPX19/j5ImKioeHh/f5+d7h4snLzNTY2Pr7/IiIiJOTk6mrq56foJ6fn7S1toaGhv///4ulXXIAAAAXdFJOU/////////////////////////////8A5kDmXgAAAD1JREFUeNokxkkSgCAAA8GguADukvz/qVRgDlONmUx0L8rdwX1DxuAkSL/1BfM0V5mqZL0GtTx+Zzz8JsAA9ZAFxigsqFUAAAAASUVORK5CYII=) no-repeat center;
--bg-collapse: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAMAAAC+Ge+yAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTlEMEIzNDQ3NzEyMTFFOUE2RDFDMzI1OUNGODkzQjEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTlEMEIzNDU3NzEyMTFFOUE2RDFDMzI1OUNGODkzQjEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOUQwQjM0Mjc3MTIxMUU5QTZEMUMzMjU5Q0Y4OTNCMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxOUQwQjM0Mzc3MTIxMUU5QTZEMUMzMjU5Q0Y4OTNCMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Ps1N6eUAAAA/UExURYiJiZ6en6mrq8nMzamqqtTY2NPX14eHh77AwPr7+4iIiN/j5N/i45+foL/CwsnLzPj6+rS1tpOTk4aGhv///0pgFIkAAAAVdFJOU///////////////////////////ACvZfeoAAAA3SURBVHjaYmDiFgEDBmEGPgFOMENYmJENyhAWFoQxhIX4YQxmFCleVpBidi4OiDksPBADAQIMAOvOBU7M/UDuAAAAAElFTkSuQmCC) no-repeat center;
}
.PropertyGrid .group {
background: #ecf0f1;
}
.PropertyGrid .group .head {
height: 20px;
box-sizing: border-box;
}
.PropertyGrid .group .head .icon {
width: 20px;
height: 20px;
float: left;
}
.PropertyGrid .group .head .icon .icon-expand {
width: 20px;
height: 20px;
background: var(--bg-expand);
display: inline-block;
}
.PropertyGrid .group .head .icon .icon-collapse {
width: 20px;
height: 20px;
background: var(--bg-collapse);
display: inline-block;
}
.PropertyGrid .group .head .title {
width: calc(100% - 20px);
height: 20px;
line-height: 20px;
padding-left: 4px;
display: inline-block;
border-bottom: 1px solid #d9d9d9;
box-sizing: border-box;
}
.PropertyGrid .group .property {
position: relative;
background: #fff;
margin-left: 20px;
}
.PropertyGrid .group .property.hide {
display: none;
}
.PropertyGrid .group .property .item {
height: 20px;
line-height: 20px;
border-bottom: 1px solid #d9d9d9;
box-sizing: border-box;
vertical-align: middle;
}
.PropertyGrid .group .property .item .label {
width: 120px;
padding: 0 4px;
display: inline-block;
box-sizing: border-box;
}
.PropertyGrid .group .property .item .value {
width: calc(100% - 120px);
padding: 0 4px;
display: inline-block;
border-left: 1px solid #d9d9d9;
box-sizing: border-box;
}

View File

@ -0,0 +1,30 @@
import './css/SVG.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* SVG
* @author tengge / https://github.com/tengge1
*/
class SVG extends React.Component {
render() {
const { className, style, ...others } = this.props;
return <svg
className={classNames('SVG', className)}
style={style}
{...others}></svg>;
}
}
SVG.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
};
SVG.defaultProps = {
className: null,
style: null,
};
export default SVG;

View File

View File

@ -0,0 +1,91 @@
import './css/DataGrid.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
import Column from '../common/Column.jsx';
import Columns from '../common/Columns.jsx';
/**
* 数据表格
* @author tengge / https://github.com/tengge1
*/
class DataGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data,
selected: null,
};
this.handleClick = this.handleClick.bind(this, props.onSelect);
}
handleClick(onSelect, event) {
const id = event.currentTarget.getAttribute('data-id');
const record = this.state.data.filter(n => n.id === id)[0];
this.setState({
selected: id,
});
onSelect && onSelect(record);
}
render() {
const { className, style, children } = this.props;
const { data, selected } = this.state;
const columns = children.props.children.map(n => {
return {
field: n.props.field,
title: n.props.title,
};
});
const header = <thead>
<tr>
{columns.map(n => {
return <td name={n.field} key={n.field}>{n.title}</td>;
})}
</tr>
</thead>;
const body = <tbody>
{data.map(n => {
return <tr className={selected === n.id ? 'selected' : null} data-id={n.id} key={n.id} onClick={this.handleClick}>
{columns.map(m => {
return <td key={m.field}>{n[m.field]}</td>;
})}
</tr>;
})}
</tbody>;
return <table className={classNames('DataGrid', className)} style={style}>
{header}
{body}
</table>;
}
}
DataGrid.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: (props, propName, componentName) => {
const children = props[propName];
if (children.type !== Columns) {
return new TypeError(`Invalid prop \`${propName}\` of type \`${children.type.name}\` supplied to \`${componentName}\`, expected \`Columns\`.`);
}
},
data: PropTypes.array,
onSelect: PropTypes.func,
};
DataGrid.defaultProps = {
className: null,
style: null,
children: null,
data: [],
onSelect: null,
};
export default DataGrid;

View File

View File

@ -0,0 +1,34 @@
import './css/Table.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 表格
* @author tengge / https://github.com/tengge1
*/
class Table extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <table
className={classNames('Table', className)}
style={style}
{...others}>
{children}
</table>;
}
}
Table.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
Table.defaultProps = {
className: null,
style: null,
children: null,
};
export default Table;

View File

@ -0,0 +1,34 @@
import './css/TableBody.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 表格内容
* @author tengge / https://github.com/tengge1
*/
class TableBody extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <tbody
className={classNames('TableBody', className)}
style={style}
{...others}>
{children}
</tbody>;
}
}
TableBody.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
TableBody.defaultProps = {
className: null,
style: null,
children: null,
};
export default TableBody;

View File

@ -0,0 +1,34 @@
import './css/TableCell.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 表格单元格
* @author tengge / https://github.com/tengge1
*/
class TableCell extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <td
className={classNames('TableCell', className)}
style={style}
{...others}>
{children}
</td>;
}
}
TableCell.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
TableCell.defaultProps = {
className: null,
style: null,
children: null,
};
export default TableCell;

View File

@ -0,0 +1,34 @@
import './css/TableHead.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 表格头部
* @author tengge / https://github.com/tengge1
*/
class TableHead extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <thead
className={classNames('TableHead', className)}
style={style}
{...others}>
{children}
</thead>;
}
}
TableHead.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
TableHead.defaultProps = {
className: null,
style: null,
children: null,
};
export default TableHead;

View File

@ -0,0 +1,34 @@
import './css/TableRow.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 表格行
* @author tengge / https://github.com/tengge1
*/
class TableRow extends React.Component {
render() {
const { className, style, children, ...others } = this.props;
return <tr
className={classNames('TableRow', className)}
style={style}
{...others}>
{children}
</tr>;
}
}
TableRow.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
};
TableRow.defaultProps = {
className: null,
style: null,
children: null,
};
export default TableRow;

View File

@ -0,0 +1,37 @@
.DataGrid {
display: inline-block;
border-collapse: collapse;
user-select: none;
cursor: default;
}
.DataGrid>thead {
background-color: #f7f7f7;
}
.DataGrid>thead>tr,
.DataGrid>tbody>tr {
height: 24px;
line-height: 24px;
}
.DataGrid>tbody>tr:nth-child(even) {
background-color: #f7f7f7;
}
.DataGrid>tbody>tr.selected {
color: #fff;
background-color: #3399ff;
}
.DataGrid>tbody>tr:hover {
color: #fff;
background-color: #3399ff;
}
.DataGrid>thead>tr>td,
.DataGrid>tbody>tr>td {
padding: 0 16px;
border: 1px solid #ebebeb;
box-sizing: border-box;
}

View File

@ -0,0 +1,6 @@
.Table {
display: inline-block;
border-collapse: collapse;
user-select: none;
cursor: default;
}

View File

@ -0,0 +1 @@
.TableBody {}

View File

@ -0,0 +1,5 @@
.TableCell {
padding: 0 16px;
border: 1px solid #ebebeb;
box-sizing: border-box;
}

View File

@ -0,0 +1,3 @@
.TableHead {
background-color: #f7f7f7;
}

View File

@ -0,0 +1,13 @@
.TableRow {
height: 24px;
line-height: 24px;
}
.TableRow:nth-child(even) {
background-color: #f7f7f7;
}
.TableBody .TableRow:hover {
color: #fff;
background-color: #3399ff;
}

View File

@ -0,0 +1,146 @@
import './css/Timeline.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
import TimelineControl from './private/TimelineControl.jsx';
import CheckBox from '../form/CheckBox.jsx';
import Label from '../form/Label.jsx';
/**
* 时间轴
* @author tengge / https://github.com/tengge1
*/
class Timeline extends React.Component {
constructor(props) {
super(props);
this.canvas = React.createRef();
this.scale = 30; // 1=30
}
render() {
const { className, style, animations, tip } = this.props;
const infos = animations.map(layer => {
return <div className={'info'} key={layer.uuid}>
<CheckBox value={layer.uuid}></CheckBox>
<Label>{layer.layerName}</Label>
</div>;
});
const layers = animations.map(layer => {
return <div className={'layer'} droppable={'true'} key={layer.uuid}>
{layer.animations.map(animation => {
const style = {
left: animation.beginTime * this.scale + 'px',
width: (animation.endTime - animation.beginTime) * this.scale + 'px',
};
return <div
className={'item'}
draggable={'true'}
droppable={'false'}
style={style}
key={animation.uuid}>
<span className={'smaller'}>{animation.name}</span>
</div>;
})}
</div>;
});
return <div className={classNames('Timeline', className)} style={style}>
<TimelineControl tip={tip}></TimelineControl>
<div className="box">
<div className="left">
{infos}
</div>
<div className="right">
<canvas className={'timeline'} ref={this.canvas}></canvas>
<div className="layers" style={{ width: '3600px' }}>
{layers}
</div>
<div className="slider"></div>
</div>
</div>
</div>;
}
componentDidMount() {
var duration = 120; // ()
var scale = this.scale;
var width = duration * scale; //
var scale5 = scale / 5; // 0.2
var margin = 0; //
var canvas = this.canvas.current;
canvas.style.width = (width + margin * 2) + 'px';
canvas.width = canvas.clientWidth;
canvas.height = 32;
var context = canvas.getContext('2d');
//
context.fillStyle = '#fafafa';
context.fillRect(0, 0, canvas.width, canvas.height);
//
context.strokeStyle = '#555';
context.beginPath();
for (var i = margin; i <= width + margin; i += scale) { //
for (var j = 0; j < 5; j++) { //
if (j === 0) { //
context.moveTo(i + scale5 * j, 22);
context.lineTo(i + scale5 * j, 30);
} else { //
context.moveTo(i + scale5 * j, 26);
context.lineTo(i + scale5 * j, 30);
}
}
}
context.stroke();
//
context.font = '12px Arial';
context.fillStyle = '#888'
for (var i = 0; i <= duration; i += 2) { //
var minute = Math.floor(i / 60);
var second = Math.floor(i % 60);
var text = (minute > 0 ? minute + ':' : '') + ('0' + second).slice(-2);
if (i === 0) {
context.textAlign = 'left';
} else if (i === duration) {
context.textAlign = 'right';
} else {
context.textAlign = 'center';
}
context.fillText(text, margin + i * scale, 16);
}
}
componentWillUnmount() {
}
}
Timeline.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
animations: PropTypes.array,
tip: PropTypes.string,
};
Timeline.defaultProps = {
className: null,
style: null,
animations: [],
tip: undefined,
};
export default Timeline;

View File

@ -0,0 +1,102 @@
.Timeline {
width: 100%;
height: 150px;
font-size: 12px;
display: flex;
flex-direction: column;
}
.Timeline>.box {
position: relative;
display: flex;
flex: 1;
flex-direction: row;
overflow: auto;
}
/* left */
.Timeline>.box>.left {
position: sticky;
left: 0;
width: 100px;
background-color: #fff;
border-right: 1px solid #ddd;
box-sizing: border-box;
padding-top: 33px;
z-index: 10;
}
.Timeline>.box>.left>.info {
position: relative;
height: 24px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
border-bottom: 1px solid #ddd;
box-sizing: border-box;
}
.Timeline>.box>.left>.info:first-child {
border-top: 1px solid #ddd;
}
/* right */
.Timeline>.box>.right {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
}
.Timeline>.box>.right>.timeline {
position: absolute;
height: 32px;
}
.Timeline>.box>.right>.layers {
position: absolute;
top: 32px;
width: 100%;
flex: 1;
border-top: 1px solid #ddd;
box-sizing: border-box;
}
.Timeline>.box>.right>.layers>.layer {
position: relative;
width: 100%;
height: 24px;
border-bottom: 1px solid #ddd;
box-sizing: border-box;
}
.Timeline>.box>.right>.layers>.layer>.item {
position: absolute;
left: 0;
width: 80px;
height: 100%;
color: #fff;
background: #2c3e50;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
border-radius: 2px;
}
.Timeline>.box>.right>.layers>.layer>.item>.smaller {
transform: scale(0.9);
}
.Timeline>.box>.right>.slider {
position: absolute;
left: 0;
top: 0;
width: 0;
height: 100%;
border: 1px solid red;
box-sizing: border-box;
}

View File

@ -0,0 +1,53 @@
import './css/TimelineControl.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
import Toolbar from '../../toolbar/Toolbar.jsx';
import ToolbarFiller from '../../toolbar/ToolbarFiller.jsx';
import ToolbarSeparator from '../../toolbar/ToolbarSeparator.jsx';
import IconButton from '../../form/IconButton.jsx';
import Label from '../../form/Label.jsx';
/**
* 时间轴控制
* @author tengge / https://github.com/tengge1
*/
class TimelineControl extends React.Component {
constructor(props) {
super(props);
}
render() {
const { className, style, tip } = this.props;
return <Toolbar className={classNames('TimelineControl', className)} style={style}>
<IconButton icon={'add'} title={'Add Layer'}></IconButton>
<IconButton icon={'delete'} title={'Delete Layer'}></IconButton>
<ToolbarSeparator></ToolbarSeparator>
<IconButton icon={'backward'} title={'Slower'}></IconButton>
<IconButton icon={'play'} title={'Play'}></IconButton>
<IconButton icon={'pause'} title={'Pause'}></IconButton>
<IconButton icon={'forward'} title={'Faster'}></IconButton>
<IconButton icon={'stop'} title={'Stop'}></IconButton>
<ToolbarSeparator></ToolbarSeparator>
<Label>00:00</Label>
<Label>X1</Label>
<ToolbarFiller></ToolbarFiller>
<Label>{tip}</Label>
</Toolbar>;
}
}
TimelineControl.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
tip: PropTypes.string,
};
TimelineControl.defaultProps = {
className: null,
style: null,
tip: 'Double click the area below to add animation.',
};
export default TimelineControl;

View File

@ -0,0 +1,7 @@
.TimelineControl>.IconButton {
border: none;
width: 24px;
height: 24px;
margin: 0 4px;
padding: 0;
}

View File

@ -0,0 +1,33 @@
import './css/Toolbar.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 工具栏
* @author tengge / https://github.com/tengge1
*/
class Toolbar extends React.Component {
render() {
const { className, style, children, direction } = this.props;
return <div
className={classNames('Toolbar', direction, className)}
style={style}>{children}</div>;
}
}
Toolbar.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
direction: PropTypes.oneOf(['horizontal', 'vertical']),
children: PropTypes.node,
};
Toolbar.defaultProps = {
className: null,
style: null,
direction: 'horizontal',
children: null,
};
export default Toolbar;

View File

@ -0,0 +1,27 @@
import './css/ToolbarFiller.css';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 工具栏填充
* @author tengge / https://github.com/tengge1
*/
class ToolbarFiller extends React.Component {
render() {
const { className, style } = this.props;
return <div className={classNames('ToolbarFiller', className)} style={style}></div>;
}
}
ToolbarFiller.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
};
ToolbarFiller.defaultProps = {
className: null,
style: null,
};
export default ToolbarFiller;

View File

@ -0,0 +1,28 @@
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
/**
* 工具栏分割线
* @author tengge / https://github.com/tengge1
*/
class ToolbarSeparator extends React.Component {
render() {
const { className, style } = this.props;
return <div className={classNames('ToolbarSeparator', className)} style={style}>
<div className='separator'></div>
</div>;
}
}
ToolbarSeparator.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
};
ToolbarSeparator.defaultProps = {
className: null,
style: null,
};
export default ToolbarSeparator;

Some files were not shown because too many files have changed in this diff Show More